Compare commits

...

34 Commits

Author SHA1 Message Date
Sammy Kerata Oina 93f2f2ab46 NOISSUE - Add DisconnectReq message and TTL support for VM creation (#428)
* feat: Add DisconnectReq message and TTL support for VM creation

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

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

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

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

---------

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

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Updated agent docs

* Fixed based on comments

* Fixed based on comments

* Initial commit, will be tested before creating a PR

* Updated agent docs

* Fixed based on comments

* Fixed based on comments

* added certificate verification

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Updated agent docs

* Fixed based on comments

* Fixed based on comments

* added certificate verification

* Fixed rebase errors

* Fixed proto issues

* fixed proto issues

* Fixed format error

* Fixed based on comments

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

* Add vtpm attestation support to agent service and server

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

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

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

* Send event notification when computation is stopped in agentService

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

* Remove redundant assignment of Stderr in qemuVM Start method

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

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

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

---------

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

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

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

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

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

* Disable SSH service and update user shell in cloud config

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

* Remove SSH server and clean up dependencies in cloud config

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

* Add firewall configuration and ensure iptables rules persist after reboot

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

* Add algo_user configuration and setup script for container execution

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

---------

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

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Fixed based on comments

* Fixed based on comments

* added certificate verification

* Initial commit, will be tested before creating a PR

* Fixed all issues

* Initial commit, will be tested before creating a PR

* Initial commit, will be tested before creating a PR

* Fixed based on comments

* Fixed rebase errors

* Fixed format error

* Fixed based on comments

* Fixed rebase errors

---------

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

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

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

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

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

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

---------

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

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

* Remove SSH server and clean up dependencies in cloud config

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

* Add firewall configuration and ensure iptables rules persist after reboot

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

* Add algo_user configuration and setup script for container execution

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

---------

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

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 21:59:48 +02:00
dependabot[bot] e217331e5e Bump github.com/docker/docker (#416)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.0.1+incompatible to 28.0.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v28.0.1...v28.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 21:59:39 +02:00
Sammy Kerata Oina 7e63921896 NOISSUE - Simplify local agent running in non sev-snp environment (#411)
* Add vtpm attestation support to agent service and server

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

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

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

* Send event notification when computation is stopped in agentService

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

* Remove redundant assignment of Stderr in qemuVM Start method

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

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

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

---------

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

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 11:38:04 +01:00
dependabot[bot] 1b4a510eb2 Bump go.opentelemetry.io/otel/trace from 1.34.0 to 1.35.0 (#402)
Bumps [go.opentelemetry.io/otel/trace](https://github.com/open-telemetry/opentelemetry-go) from 1.34.0 to 1.35.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.34.0...v1.35.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/otel/trace
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 11:34:22 +01:00
dependabot[bot] ff0fd71e7d Bump golang.org/x/crypto from 0.35.0 to 0.36.0 (#400)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/crypto/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 11:32:05 +01:00
Danko Miladinovic bfaa489bdb NOISSUE - Change the CLI SNP verification (#406) 2025-03-19 11:30:49 +01:00
Sammy Kerata Oina c14f1d7b6c COCOS-391- GCP Attestation policy (#405)
* Add AgentGrpcHost configuration to agent server

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

* Add SHA1 support to PcrValues and implement GCP attestation functions

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

* Add GCP attestation policy and OVMF download commands

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

* Add vTPM attestation support and update protobuf versions

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

* Remove Host field from AgentConfig and update related references

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

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

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

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

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

* Update OVMF object name format in GCP attestation package

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

* Refactor attestation policy structure to use nested Config field

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

---------

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

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

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

* fix cli test

* remove stdout from cmd config struct

* fix manager test

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

* updated readme.md for issue 390

* resolved issue 390

* updated readme.md for issue 390

* implemented suggested changes

* refactored code so it passes the linter test

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

* refactored code so it passes the linter test

* fixed the test Run_-_Failure_Execution

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

Add testing to igvm measurements

Remove trailing white space

Improve testing

Resolve PR comments

Add measure to cli

Add README  for feature

Fix PR comments

Added new line to shell script

Add measurement interface

Fix ci

Refactor code for IgvmMeasurement to become a CLI dependency

Refactor code for IgvmMeasurement to become a CLI dependency

Refactor based on ci failures

Fix error handling

Add header

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

* rebase and changed atls for vtpm

* deleted unused code

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

* fixe manager proto version

* fix agent tests

* fix server agent test

* fix attestation test

* fix attestation test gofumpt

* created dummy RWC for TPM

* fix comment

* add default PCR values

* rebase main

* fix rust ci and missing header

* changed embedded  attestation to VMPL 2

* fix unused impot

* fix pkg test

* address attestation type

* fix agent attestation test

* add prc15 check

* fix comments

* fix cli tests

* add doc

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

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

* fix manager reading attestation policy

* refactor PCR value checks and update attestation policy values

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

* fix tests for sev and grpc

---------

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 13:08:55 +01:00
Sammy Kerata Oina 7d063aa48d NOISSUE - Dependabot docker fix (#386)
* Bump github.com/docker/docker

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

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

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

* Update ImageLoad call to use client.ImageLoadWithQuiet option

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

---------

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

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 15:16:24 +01:00
dependabot[bot] 1210e238b4 Bump golang.org/x/term from 0.28.0 to 0.29.0 (#382)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.28.0 to 0.29.0.
- [Commits](https://github.com/golang/term/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 15:16:15 +01:00
dependabot[bot] 93f9c4df1c Bump github.com/spf13/cobra from 1.8.1 to 1.9.1 (#384)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-26 15:16:01 +01:00
Sammy Kerata Oina 913c6138cf NOISSUE - Update Dependabot configuration and Go dependencies (#378)
* Update Dependabot configuration and Go dependencies; modify test command to include embed tag

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

* Refactor CVMSClient computation handling and improve test message queue capacity

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

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

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

---------

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

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

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

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

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

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

* Refactor CVMSClient computation handling and improve test case structure

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

* Refactor messageQueue channel in TestManagerClient_Process for improved concurrency handling

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Rename sendMessageWithRetry to sendStreamMessage for clarity and consistency in CVMSClient

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

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

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

---------

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

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

* Add uuid dependency to go.work.sum

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

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

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

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

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

* Update mockery configuration and enhance QuoteProvider mock with additional methods

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

* Update error message in TestAgentClientIntegration for clarity

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

---------

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

CLI attestation changes

Add all modes to CLI changes updates

Remove old attestation validateion command

Make updates based on PR reviews

Disable depreciated linter

Linter fixes

Resolve conflicts

resolve go.mod errors

Use previous version of go-sev-guest

Modify tests

Remove tdx attestation

Fix error messages printing

Add tests

Fix CI failures

Do proper error handling

* Fix CI failures

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

* Add kernel changes

* Formatting changes

* Modify go-sev-guest version

* Uncomment out kernel version
2025-02-10 15:36:34 +01:00
115 changed files with 7206 additions and 1790 deletions
-4
View File
@@ -16,7 +16,3 @@ updates:
schedule:
interval: "weekly"
day: "monday"
groups:
go-dependency:
patterns:
- "*"
+3 -3
View File
@@ -34,7 +34,7 @@ jobs:
- name: Set up protoc
run: |
PROTOC_VERSION=29.0
PROTOC_GEN_VERSION=v1.36.0
PROTOC_GEN_VERSION=v1.36.5
PROTOC_GRPC_VERSION=v1.5.1
# Download and install protoc
@@ -55,7 +55,7 @@ jobs:
- name: Set up Cocos-AI
run: |
# Rename .pb.go files to .pb.go.tmp to prevent conflicts
for p in $(ls pkg/manager/*.pb.go); do
for p in $(ls manager/*.pb.go); do
mv $p $p.tmp
done
@@ -67,7 +67,7 @@ jobs:
make protoc
# Compare generated Go files with the original ones
for p in $(ls pkg/manager/*.pb.go); do
for p in $(ls manager/*.pb.go); do
if ! cmp -s $p $p.tmp; then
echo "Proto file and generated Go file $p are out of sync!"
exit 1
+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 -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
run: go test --tags embed -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 -63
View File
@@ -1,80 +1,100 @@
run:
timeout: 3m
issues:
max-issues-per-linter: 10
max-same-issues: 10
linters-settings:
importas:
no-unaliased: true
no-extra-aliases: false
alias:
- pkg: github.com/absmach/magistrala/logger
alias: mglog
gocritic:
enabled-checks:
- dupImport
- importShadow
- httpNoBody
- paramTypeCombine
- emptyStringTest
- builtinShadow
- exposedSyncMutex
disabled-checks:
- appendAssign
enabled-tags:
- diagnostic
disabled-tags:
- performance
- style
- experimental
- opinionated
stylecheck:
checks: ["-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022"]
goheader:
template: |-
Copyright (c) Ultraviolet
SPDX-License-Identifier: Apache-2.0
version: "2"
linters:
disable-all: true
default: none
enable:
- importas
- gocritic
- gosimple
- errcheck
- govet
- unused
- goconst
- godot
- godox
- ineffassign
- misspell
- stylecheck
- whitespace
- gci
- gofmt
- goimports
- loggercheck
- goheader
- asasalint
- asciicheck
- bidichk
- contextcheck
- copyloopvar
- decorder
- dogsled
- dupword
- errcheck
- errchkjson
- errname
- execinquery
- copyloopvar
- ginkgolinter
- gocheckcompilerdirectives
- gofumpt
- goconst
- gocritic
- godot
- godox
- goheader
- goprintffuncname
- govet
- importas
- ineffassign
- loggercheck
- makezero
- mirror
- misspell
- nakedret
- dupword
- staticcheck
- unused
- whitespace
settings:
gocritic:
enabled-checks:
- dupImport
- importShadow
- httpNoBody
- paramTypeCombine
- emptyStringTest
- builtinShadow
- exposedSyncMutex
disabled-checks:
- appendAssign
enabled-tags:
- diagnostic
disabled-tags:
- performance
- style
- experimental
- opinionated
goheader:
template: |-
Copyright (c) Ultraviolet
SPDX-License-Identifier: Apache-2.0
importas:
alias:
- pkg: github.com/absmach/magistrala/logger
alias: mglog
no-unaliased: true
no-extra-aliases: false
staticcheck:
checks:
- -ST1000
- -ST1003
- -ST1020
- -ST1021
- -ST1022
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- makezero
text: with non-zero initialized length
paths:
- third_party$
- builtin$
- examples$
issues:
max-issues-per-linter: 10
max-same-issues: 10
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+7 -1
View File
@@ -12,6 +12,7 @@ CONFIG_DIR ?= /etc/cocos
SERVICE_NAME ?= cocos-manager
SERVICE_DIR ?= /etc/systemd/system
SERVICE_FILE = init/systemd/$(SERVICE_NAME).service
IGVM_BUILD_SCRIPT := ./scripts/igvmmeasure/igvm.sh
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
@@ -27,8 +28,9 @@ endef
all: $(SERVICES)
$(SERVICES):
$(SERVICES):
$(call compile_service,$@)
@if [ "$@" = "cli" ] || [ "$@" = "manager" ]; then $(MAKE) build-igvm; fi
$(ATTESTATION_POLICY):
$(MAKE) -C ./scripts/attestation_policy
@@ -61,3 +63,7 @@ stop:
install_service:
sudo install -m 644 $(SERVICE_FILE) $(SERVICE_DIR)/$(SERVICE_NAME).service
sudo systemctl daemon-reload
build-igvm:
@echo "Running build script for igvmmeasure..."
@$(IGVM_BUILD_SCRIPT)
+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, 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. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union. Neither the European Union nor the granting authority can be held responsible for them.
+10 -10
View File
@@ -6,16 +6,16 @@ Agent service provides a barebones HTTP and gRPC API and Service interface imple
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
| Variable | Description | Default |
| ----------------------------- | ------------------------------------------------------ | ------------------------------ |
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | info |
| AGENT_GRPC_HOST | Agent service gRPC host | "" |
| AGENT_GRPC_PORT | Agent service gRPC port | 7002 |
| AGENT_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
| AGENT_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
| AGENT_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | "" |
| AGENT_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | "" |
| Variable | Description | Default |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | debug |
| AGENT_CVM_GRPC_HOST | Agent service gRPC host | "" |
| AGENT_CVM_GRPC_PORT | Agent service gRPC port | 7001 |
| AGENT_CVM_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
| AGENT_CVM_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
| AGENT_CVM_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | "" |
| AGENT_CVM_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | "" |
| AGENT_CVM_CA_URL | URL for CA service, if provided it will be used for certificate generation, used only with aTLS at the moment | "" |
## Deployment
+53 -34
View File
@@ -3,7 +3,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.0
// protoc-gen-go v1.36.5
// protoc v5.29.0
// source: agent/agent.proto
@@ -14,6 +14,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -281,7 +282,9 @@ func (x *ResultResponse) GetFile() []byte {
type AttestationRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ReportData []byte `protobuf:"bytes,1,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be of length 64.
TeeNonce []byte `protobuf:"bytes,1,opt,name=teeNonce,proto3" json:"teeNonce,omitempty"` // Should be less or equal 64 bytes.
VtpmNonce []byte `protobuf:"bytes,2,opt,name=vtpmNonce,proto3" json:"vtpmNonce,omitempty"` // Should be less or equal 32 bytes.
Type int32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -316,13 +319,27 @@ func (*AttestationRequest) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{6}
}
func (x *AttestationRequest) GetReportData() []byte {
func (x *AttestationRequest) GetTeeNonce() []byte {
if x != nil {
return x.ReportData
return x.TeeNonce
}
return nil
}
func (x *AttestationRequest) GetVtpmNonce() []byte {
if x != nil {
return x.VtpmNonce
}
return nil
}
func (x *AttestationRequest) GetType() int32 {
if x != nil {
return x.Type
}
return 0
}
type AttestationResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
@@ -369,7 +386,7 @@ func (x *AttestationResponse) GetFile() []byte {
var File_agent_agent_proto protoreflect.FileDescriptor
var file_agent_agent_proto_rawDesc = []byte{
var file_agent_agent_proto_rawDesc = string([]byte{
0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x0b, 0x41, 0x6c,
0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67,
@@ -386,40 +403,43 @@ var file_agent_agent_proto_rawDesc = []byte{
0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x29,
0x0a, 0x13, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c,
0x67, 0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12,
0x33, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14,
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,
0x48, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19,
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61,
0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x62, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x76, 0x74, 0x70,
0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x76, 0x74,
0x70, 0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x29, 0x0a, 0x13, 0x41,
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12,
0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04,
0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74,
0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28,
0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0b,
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65, 0x6e,
0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
file_agent_agent_proto_rawDescOnce sync.Once
file_agent_agent_proto_rawDescData = file_agent_agent_proto_rawDesc
file_agent_agent_proto_rawDescData []byte
)
func file_agent_agent_proto_rawDescGZIP() []byte {
file_agent_agent_proto_rawDescOnce.Do(func() {
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_agent_proto_rawDescData)
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)))
})
return file_agent_agent_proto_rawDescData
}
@@ -460,7 +480,7 @@ func file_agent_agent_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_agent_agent_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)),
NumEnums: 0,
NumMessages: 8,
NumExtensions: 0,
@@ -471,7 +491,6 @@ func file_agent_agent_proto_init() {
MessageInfos: file_agent_agent_proto_msgTypes,
}.Build()
File_agent_agent_proto = out.File
file_agent_agent_proto_rawDesc = nil
file_agent_agent_proto_goTypes = nil
file_agent_agent_proto_depIdxs = nil
}
+3 -1
View File
@@ -36,7 +36,9 @@ message ResultResponse {
}
message AttestationRequest {
bytes report_data = 1; // Should be of length 64.
bytes teeNonce = 1; // Should be less or equal 64 bytes.
bytes vtpmNonce = 2; // Should be less or equal 32 bytes.
int32 type = 3;
}
message AttestationResponse {
+1 -1
View File
@@ -62,7 +62,7 @@ func (d *docker) Run() error {
ctx := context.Background()
// Load the Docker image from the tar file.
resp, err := cli.ImageLoad(ctx, imageFile, true)
resp, err := cli.ImageLoad(ctx, imageFile, client.ImageLoadWithQuiet(true))
if err != nil {
return fmt.Errorf("could not load Docker image from file: %v", err)
}
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/go-kit/kit/endpoint"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
)
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
@@ -70,7 +71,7 @@ func attestationEndpoint(svc agent.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return attestationRes{}, err
}
file, err := svc.Attestation(ctx, req.ReportData)
file, err := svc.Attestation(ctx, req.TeeNonce, req.VtpmNonce, config.AttestationType(req.AttType))
if err != nil {
return attestationRes{}, err
}
+5 -4
View File
@@ -9,6 +9,7 @@ import (
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/mocks"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"golang.org/x/crypto/sha3"
)
@@ -141,11 +142,11 @@ func TestAttestationEndpoint(t *testing.T) {
}{
{
name: "Success",
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
},
{
name: "Service Error",
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
expectedErr: true,
},
}
@@ -153,9 +154,9 @@ func TestAttestationEndpoint(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == svcErr {
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, errors.New("")).Once()
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, errors.New("")).Once()
} else {
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, nil).Once()
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, nil).Once()
}
endpoint := attestationEndpoint(svc)
res, err := endpoint(context.Background(), tt.req)
+13 -2
View File
@@ -4,6 +4,10 @@ package grpc
import (
"errors"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
type algoReq struct {
@@ -38,9 +42,16 @@ func (req resultReq) validate() error {
}
type attestationReq struct {
ReportData [64]byte
TeeNonce [quoteprovider.Nonce]byte
VtpmNonce [vtpm.Nonce]byte
AttType config.AttestationType
}
func (req attestationReq) validate() error {
return nil
switch req.AttType {
case config.SNP, config.VTPM, config.SNPvTPM:
return nil
default:
return errors.New("invalid attestation type in attestation request")
}
}
+21 -3
View File
@@ -11,6 +11,9 @@ import (
"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/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
@@ -21,6 +24,11 @@ const (
FileSizeKey = "file-size"
)
var (
ErrTEENonceLength = errors.New("malformed report data, expect less or equal to 64 bytes")
ErrVTpmNonceLength = errors.New("malformed vTPM nonce, expect less or equal to 32 bytes")
)
var _ agent.AgentServiceServer = (*grpcServer)(nil)
type grpcServer struct {
@@ -96,10 +104,20 @@ func encodeResultResponse(_ context.Context, response interface{}) (interface{},
func decodeAttestationRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*agent.AttestationRequest)
if len(req.ReportData) != agent.ReportDataSize {
return nil, errors.New("malformed report data, expect 64 bytes")
var reportData [quoteprovider.Nonce]byte
var nonce [vtpm.Nonce]byte
if len(req.TeeNonce) > quoteprovider.Nonce {
return nil, ErrTEENonceLength
}
return attestationReq{ReportData: [agent.ReportDataSize]byte(req.ReportData)}, nil
if len(req.VtpmNonce) > vtpm.Nonce {
return nil, ErrVTpmNonceLength
}
copy(reportData[:], req.TeeNonce)
copy(nonce[:], req.VtpmNonce)
return attestationReq{TeeNonce: reportData, VtpmNonce: nonce, AttType: config.AttestationType(req.Type)}, nil
}
func encodeAttestationResponse(_ context.Context, response interface{}) (interface{}, error) {
+11 -6
View File
@@ -11,6 +11,9 @@ 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/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
@@ -151,10 +154,12 @@ func TestAttestation(t *testing.T) {
mockStream := &MockAgentService_AttestationServer{ctx: context.Background()}
mockStream.On("Send", mock.AnythingOfType("*agent.AttestationResponse")).Return(nil)
reportData := [agent.ReportDataSize]byte{}
mockService.On("Attestation", mock.Anything, reportData).Return([]byte("attestation data"), nil)
reportData := [quoteprovider.Nonce]byte{}
vtpmNonce := [vtpm.Nonce]byte{}
attestationType := config.SNP
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return([]byte("attestation data"), nil)
err := server.Attestation(&agent.AttestationRequest{ReportData: reportData[:]}, mockStream)
err := server.Attestation(&agent.AttestationRequest{TeeNonce: reportData[:]}, mockStream)
assert.NoError(t, err)
mockService.AssertExpectations(t)
@@ -199,11 +204,11 @@ func TestEncodeResultResponse(t *testing.T) {
}
func TestDecodeAttestationRequest(t *testing.T) {
reportData := [agent.ReportDataSize]byte{}
req := &agent.AttestationRequest{ReportData: reportData[:]}
nonce := [quoteprovider.Nonce]byte{}
req := &agent.AttestationRequest{TeeNonce: nonce[:]}
decoded, err := decodeAttestationRequest(context.Background(), req)
assert.NoError(t, err)
assert.Equal(t, attestationReq{ReportData: reportData}, decoded)
assert.Equal(t, attestationReq{TeeNonce: nonce}, decoded)
}
func TestEncodeAttestationResponse(t *testing.T) {
+14 -2
View File
@@ -13,6 +13,9 @@ import (
"time"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
var _ agent.Service = (*loggingMiddleware)(nil)
@@ -27,6 +30,15 @@ func LoggingMiddleware(svc agent.Service, logger *slog.Logger) agent.Service {
return &loggingMiddleware{logger, svc}
}
// State implements agent.Service.
func (lm *loggingMiddleware) State() (state string) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method State took %s to complete with state %s", time.Since(begin), state)
lm.logger.Debug(message)
}(time.Now())
return lm.svc.State()
}
// InitComputation implements agent.Service.
func (lm *loggingMiddleware) InitComputation(ctx context.Context, cmp agent.Computation) (err error) {
defer func(begin time.Time) {
@@ -94,7 +106,7 @@ func (lm *loggingMiddleware) Result(ctx context.Context) (response []byte, err e
return lm.svc.Result(ctx)
}
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) (response []byte, err error) {
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) (response []byte, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method Attestation took %s to complete", time.Since(begin))
if err != nil {
@@ -104,5 +116,5 @@ func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.Attestation(ctx, reportData)
return lm.svc.Attestation(ctx, reportData, nonce, attType)
}
+15 -2
View File
@@ -12,6 +12,9 @@ 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/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
var _ agent.Service = (*metricsMiddleware)(nil)
@@ -32,6 +35,16 @@ func MetricsMiddleware(svc agent.Service, counter metrics.Counter, latency metri
}
}
// State implements agent.Service.
func (ms *metricsMiddleware) State() string {
defer func(begin time.Time) {
ms.counter.With("method", "state").Add(1)
ms.latency.With("method", "state").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.State()
}
// InitComputation implements agent.Service.
func (ms *metricsMiddleware) InitComputation(ctx context.Context, cmp agent.Computation) error {
defer func(begin time.Time) {
@@ -79,11 +92,11 @@ func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
return ms.svc.Result(ctx)
}
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) ([]byte, error) {
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
defer func(begin time.Time) {
ms.counter.With("method", "attestation").Add(1)
ms.latency.With("method", "attestation").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Attestation(ctx, reportData)
return ms.svc.Attestation(ctx, reportData, nonce, attType)
}
+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
-1
View File
@@ -13,7 +13,6 @@ import (
var _ fmt.Stringer = (*Datasets)(nil)
type AgentConfig struct {
Host string `json:"host,omitempty"`
Port string `json:"port,omitempty"`
CertFile string `json:"cert_file,omitempty"`
KeyFile string `json:"server_key,omitempty"`
-1
View File
@@ -106,7 +106,6 @@ func TestDecompressToContext(t *testing.T) {
func TestAgentConfigJSON(t *testing.T) {
config := AgentConfig{
Host: "localhost",
Port: "8080",
CertFile: "cert.pem",
KeyFile: "key.pem",
+122 -23
View File
@@ -11,17 +11,29 @@ import (
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/cvms"
"github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
"github.com/ultravioletrs/cocos/agent/cvms/server"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/proto"
)
const (
reconnectInterval = 5 * time.Second
sendTimeout = 5 * time.Second
pendingMsgFile = "pending_messages.json"
)
var (
errCorruptedManifest = errors.New("received manifest may be corrupted")
errUnknonwMessageType = errors.New("unknown message type")
sendTimeout = 5 * time.Second
)
type PendingMessage struct {
Message *cvms.ClientStreamMessage
Time time.Time
}
type CVMSClient struct {
mu sync.Mutex
stream cvms.Service_ProcessClient
@@ -30,21 +42,55 @@ type CVMSClient struct {
logger *slog.Logger
runReqManager *runRequestManager
sp server.AgentServer
storage storage.Storage
reconnectFn func(context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error)
grpcClient pkggrpc.Client
}
// NewClient returns new gRPC client instance.
func NewClient(stream cvms.Service_ProcessClient, svc agent.Service, messageQueue chan *cvms.ClientStreamMessage, logger *slog.Logger, sp server.AgentServer) CVMSClient {
return CVMSClient{
func NewClient(stream cvms.Service_ProcessClient, svc agent.Service, messageQueue chan *cvms.ClientStreamMessage, logger *slog.Logger, sp server.AgentServer, storageDir string, reconnectFn func(context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error), grpcClient pkggrpc.Client) (*CVMSClient, error) {
store, err := storage.NewFileStorage(storageDir)
if err != nil {
return nil, err
}
return &CVMSClient{
stream: stream,
svc: svc,
messageQueue: messageQueue,
logger: logger,
runReqManager: newRunRequestManager(),
sp: sp,
}
storage: store,
reconnectFn: reconnectFn,
grpcClient: grpcClient,
}, nil
}
func (client *CVMSClient) Process(ctx context.Context, cancel context.CancelFunc) error {
for {
err := client.processWithRetry(ctx)
if ctx.Err() != nil {
return ctx.Err()
}
client.logger.Info("Connection lost, attempting to reconnect...", "error", err)
time.Sleep(reconnectInterval)
grpcClient, stream, err := client.reconnectFn(ctx)
if err != nil {
client.logger.Error("Failed to reconnect", "error", err)
continue
}
client.mu.Lock()
client.stream = stream
client.grpcClient = grpcClient
client.mu.Unlock()
}
}
func (client *CVMSClient) processWithRetry(ctx context.Context) error {
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
@@ -75,18 +121,87 @@ func (client *CVMSClient) handleIncomingMessages(ctx context.Context) error {
}
}
func (client *CVMSClient) handleOutgoingMessages(ctx context.Context) error {
pendingMsgs, err := client.storage.Load()
if err != nil {
client.logger.Error("Failed to load pending messages", "error", err)
} else {
client.sendPendingMessages(pendingMsgs)
}
for {
select {
case <-ctx.Done():
return ctx.Err()
case msg := <-client.messageQueue:
if err := client.sendStreamMessage(msg); err != nil {
if err := client.storage.Add(msg); err != nil {
client.logger.Error("Failed to store pending message", "error", err)
}
client.logger.Error("Failed to send message, stored for retry", "error", err)
}
}
}
}
func (client *CVMSClient) sendStreamMessage(msg *cvms.ClientStreamMessage) error {
client.mu.Lock()
defer client.mu.Unlock()
return client.stream.Send(msg)
}
func (client *CVMSClient) sendPendingMessages(pending []storage.Message) {
for _, pm := range pending {
if err := client.sendStreamMessage(pm.Message); err != nil {
if err := client.storage.Add(pm.Message); err != nil {
client.logger.Error("Failed to store pending message", "error", err)
}
client.logger.Error("Failed to resend pending message", "error", err)
} else {
client.logger.Info("Successfully resent pending message")
}
}
if err := client.storage.Clear(); err != nil {
client.logger.Error("Failed to clear pending messages", "error", err)
}
}
func (client *CVMSClient) processIncomingMessage(ctx context.Context, req *cvms.ServerStreamMessage) error {
switch mes := req.Message.(type) {
case *cvms.ServerStreamMessage_RunReqChunks:
return client.handleRunReqChunks(ctx, mes)
case *cvms.ServerStreamMessage_StopComputation:
go client.handleStopComputation(ctx, mes)
case *cvms.ServerStreamMessage_AgentStateReq:
client.handleAgentStateReq(mes)
case *cvms.ServerStreamMessage_DisconnectReq:
client.logger.Info("Received disconnect request")
client.mu.Lock()
if err := client.grpcClient.Close(); err != nil {
client.logger.Error("Failed to close gRPC client", "error", err)
}
client.mu.Unlock()
default:
return errUnknonwMessageType
}
return nil
}
func (client *CVMSClient) handleAgentStateReq(mes *cvms.ServerStreamMessage_AgentStateReq) {
state := client.svc.State()
msg := &cvms.ClientStreamMessage_AgentStateRes{
AgentStateRes: &cvms.AgentStateRes{
State: state,
Id: mes.AgentStateReq.Id,
},
}
client.sendMessage(&cvms.ClientStreamMessage{Message: msg})
}
func (client *CVMSClient) handleRunReqChunks(ctx context.Context, msg *cvms.ServerStreamMessage_RunReqChunks) error {
buffer, complete := client.runReqManager.addChunk(msg.RunReqChunks.Id, msg.RunReqChunks.Data, msg.RunReqChunks.IsLast)
@@ -147,16 +262,14 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
},
}
err := client.sp.Start(ctx, agent.AgentConfig{
if err := client.sp.Start(agent.AgentConfig{
Port: runReq.AgentConfig.Port,
Host: runReq.AgentConfig.Host,
CertFile: runReq.AgentConfig.CertFile,
KeyFile: runReq.AgentConfig.KeyFile,
ServerCAFile: runReq.AgentConfig.ServerCaFile,
ClientCAFile: runReq.AgentConfig.ClientCaFile,
AttestedTls: runReq.AgentConfig.AttestedTls,
}, ac)
if err != nil {
}, ac); err != nil {
client.logger.Warn(err.Error())
runRes.RunRes.Error = err.Error()
}
@@ -175,28 +288,14 @@ func (client *CVMSClient) handleStopComputation(ctx context.Context, mes *cvms.S
}
client.mu.Lock()
defer client.mu.Unlock()
if err := client.sp.Stop(); err != nil {
msg.StopComputationRes.Message = err.Error()
}
client.mu.Unlock()
client.sendMessage(&cvms.ClientStreamMessage{Message: msg})
}
func (client *CVMSClient) handleOutgoingMessages(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case mes := <-client.messageQueue:
if err := client.stream.Send(mes); err != nil {
return err
}
}
}
}
func (client *CVMSClient) sendMessage(mes *cvms.ClientStreamMessage) {
ctx, cancel := context.WithTimeout(context.Background(), sendTimeout)
defer cancel()
+27 -14
View File
@@ -13,6 +13,8 @@ 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"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
)
@@ -32,16 +34,16 @@ func (m *mockStream) Send(msg *cvms.ClientStreamMessage) error {
return args.Error(0)
}
func TestManagerClient_Process1(t *testing.T) {
func TestManagerClient_Process(t *testing.T) {
tests := []struct {
name string
setupMocks func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider)
setupMocks func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer)
expectError bool
errorMsg string
}{
{
name: "Stop computation",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider) {
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_StopComputation{
StopComputation: &cvms.StopComputation{},
@@ -56,7 +58,7 @@ func TestManagerClient_Process1(t *testing.T) {
},
{
name: "Run request chunks",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider) {
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
Message: &cvms.ServerStreamMessage_RunReqChunks{
RunReqChunks: &cvms.RunReqChunks{},
@@ -69,7 +71,7 @@ func TestManagerClient_Process1(t *testing.T) {
},
{
name: "Receive error",
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider) {
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{}, assert.AnError)
},
expectError: true,
@@ -80,18 +82,25 @@ func TestManagerClient_Process1(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
mockStream := new(mockStream)
mockSvc := new(mocks.Service)
mockServerSvc := new(servermocks.AgentServerProvider)
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
mockServerSvc := new(servermocks.AgentServer)
messageQueue := make(chan *cvms.ClientStreamMessage)
logger := mglog.NewMock()
client := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc)
go func() {
<-messageQueue
}()
grpcClient := new(clientmocks.Client)
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
assert.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
tc.setupMocks(mockStream, mockSvc, mockServerSvc)
err := client.Process(ctx, cancel)
err = client.Process(ctx, cancel)
if tc.expectError {
assert.Error(t, err)
@@ -108,11 +117,13 @@ func TestManagerClient_Process1(t *testing.T) {
func TestManagerClient_handleRunReqChunks(t *testing.T) {
mockStream := new(mockStream)
mockSvc := new(mocks.Service)
mockServerSvc := new(servermocks.AgentServerProvider)
mockServerSvc := new(servermocks.AgentServer)
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
logger := mglog.NewMock()
grpcClient := new(clientmocks.Client)
client := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc)
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",
@@ -137,7 +148,7 @@ func TestManagerClient_handleRunReqChunks(t *testing.T) {
mockSvc.On("InitComputation", mock.Anything, mock.Anything).Return(nil)
mockServerSvc.On("Start", mock.Anything, mock.Anything, mock.Anything).Return(nil)
err := client.handleRunReqChunks(context.Background(), chunk1)
err = client.handleRunReqChunks(context.Background(), chunk1)
assert.NoError(t, err)
err = client.handleRunReqChunks(context.Background(), chunk2)
@@ -158,11 +169,13 @@ func TestManagerClient_handleRunReqChunks(t *testing.T) {
func TestManagerClient_handleStopComputation(t *testing.T) {
mockStream := new(mockStream)
mockSvc := new(mocks.Service)
mockServerSvc := new(servermocks.AgentServerProvider)
mockServerSvc := new(servermocks.AgentServer)
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
logger := mglog.NewMock()
grpcClient := new(clientmocks.Client)
client := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc)
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
assert.NoError(t, err)
stopReq := &cvms.ServerStreamMessage_StopComputation{
StopComputation: &cvms.StopComputation{
@@ -0,0 +1,234 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
cvms "github.com/ultravioletrs/cocos/agent/cvms"
storage "github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
)
// Storage is an autogenerated mock type for the Storage type
type Storage struct {
mock.Mock
}
type Storage_Expecter struct {
mock *mock.Mock
}
func (_m *Storage) EXPECT() *Storage_Expecter {
return &Storage_Expecter{mock: &_m.Mock}
}
// Add provides a mock function with given fields: msg
func (_m *Storage) Add(msg *cvms.ClientStreamMessage) error {
ret := _m.Called(msg)
if len(ret) == 0 {
panic("no return value specified for Add")
}
var r0 error
if rf, ok := ret.Get(0).(func(*cvms.ClientStreamMessage) error); ok {
r0 = rf(msg)
} else {
r0 = ret.Error(0)
}
return r0
}
// Storage_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'
type Storage_Add_Call struct {
*mock.Call
}
// Add is a helper method to define mock.On call
// - msg *cvms.ClientStreamMessage
func (_e *Storage_Expecter) Add(msg interface{}) *Storage_Add_Call {
return &Storage_Add_Call{Call: _e.mock.On("Add", msg)}
}
func (_c *Storage_Add_Call) Run(run func(msg *cvms.ClientStreamMessage)) *Storage_Add_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*cvms.ClientStreamMessage))
})
return _c
}
func (_c *Storage_Add_Call) Return(_a0 error) *Storage_Add_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Storage_Add_Call) RunAndReturn(run func(*cvms.ClientStreamMessage) error) *Storage_Add_Call {
_c.Call.Return(run)
return _c
}
// Clear provides a mock function with no fields
func (_m *Storage) Clear() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Clear")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Storage_Clear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clear'
type Storage_Clear_Call struct {
*mock.Call
}
// Clear is a helper method to define mock.On call
func (_e *Storage_Expecter) Clear() *Storage_Clear_Call {
return &Storage_Clear_Call{Call: _e.mock.On("Clear")}
}
func (_c *Storage_Clear_Call) Run(run func()) *Storage_Clear_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Storage_Clear_Call) Return(_a0 error) *Storage_Clear_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Storage_Clear_Call) RunAndReturn(run func() error) *Storage_Clear_Call {
_c.Call.Return(run)
return _c
}
// Load provides a mock function with no fields
func (_m *Storage) Load() ([]storage.Message, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Load")
}
var r0 []storage.Message
var r1 error
if rf, ok := ret.Get(0).(func() ([]storage.Message, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() []storage.Message); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]storage.Message)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Storage_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load'
type Storage_Load_Call struct {
*mock.Call
}
// Load is a helper method to define mock.On call
func (_e *Storage_Expecter) Load() *Storage_Load_Call {
return &Storage_Load_Call{Call: _e.mock.On("Load")}
}
func (_c *Storage_Load_Call) Run(run func()) *Storage_Load_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Storage_Load_Call) Return(_a0 []storage.Message, _a1 error) *Storage_Load_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Storage_Load_Call) RunAndReturn(run func() ([]storage.Message, error)) *Storage_Load_Call {
_c.Call.Return(run)
return _c
}
// Save provides a mock function with given fields: messages
func (_m *Storage) Save(messages []storage.Message) error {
ret := _m.Called(messages)
if len(ret) == 0 {
panic("no return value specified for Save")
}
var r0 error
if rf, ok := ret.Get(0).(func([]storage.Message) error); ok {
r0 = rf(messages)
} else {
r0 = ret.Error(0)
}
return r0
}
// Storage_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
type Storage_Save_Call struct {
*mock.Call
}
// Save is a helper method to define mock.On call
// - messages []storage.Message
func (_e *Storage_Expecter) Save(messages interface{}) *Storage_Save_Call {
return &Storage_Save_Call{Call: _e.mock.On("Save", messages)}
}
func (_c *Storage_Save_Call) Run(run func(messages []storage.Message)) *Storage_Save_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]storage.Message))
})
return _c
}
func (_c *Storage_Save_Call) Return(_a0 error) *Storage_Save_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Storage_Save_Call) RunAndReturn(run func([]storage.Message) error) *Storage_Save_Call {
_c.Call.Return(run)
return _c
}
// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewStorage(t interface {
mock.TestingT
Cleanup(func())
}) *Storage {
mock := &Storage{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package storage
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
"github.com/ultravioletrs/cocos/agent/cvms"
)
// Message represents a pending message with its timestamp.
type Message struct {
Message *cvms.ClientStreamMessage
Time time.Time
}
// Storage defines the interface for message persistence operations.
type Storage interface {
// Load retrieves all pending messages from storage.
Load() ([]Message, error)
// Save persists the given messages to storage.
Save(messages []Message) error
// Add appends a new message to storage.
Add(msg *cvms.ClientStreamMessage) error
// Clear removes all messages from storage.
Clear() error
}
// FileStorage implements Storage interface using file-based persistence.
type FileStorage struct {
mu sync.Mutex
path string
msgs []Message
}
// NewFileStorage creates a new file-based storage instance.
func NewFileStorage(storageDir string) (*FileStorage, error) {
if err := os.MkdirAll(storageDir, 0o755); err != nil {
return nil, err
}
return &FileStorage{
path: filepath.Join(storageDir, "pending_messages.json"),
msgs: make([]Message, 0),
}, nil
}
func (fs *FileStorage) Load() ([]Message, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
data, err := os.ReadFile(fs.path)
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &fs.msgs); err != nil {
return nil, err
}
return fs.msgs, nil
}
func (fs *FileStorage) Save(messages []Message) error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.msgs = messages
data, err := json.Marshal(messages)
if err != nil {
return err
}
return os.WriteFile(fs.path, data, 0o644)
}
func (fs *FileStorage) Add(msg *cvms.ClientStreamMessage) error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.msgs = append(fs.msgs, Message{
Message: msg,
Time: time.Now(),
})
data, err := json.Marshal(fs.msgs)
if err != nil {
return err
}
return os.WriteFile(fs.path, data, 0o644)
}
func (fs *FileStorage) Clear() error {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.msgs = make([]Message, 0)
return os.WriteFile(fs.path, []byte("[]"), 0o644)
}
+513 -224
View File
File diff suppressed because it is too large Load Diff
+28 -7
View File
@@ -13,6 +13,15 @@ service Service {
rpc Process(stream ClientStreamMessage) returns (stream ServerStreamMessage) {}
}
message AgentStateReq {
string id = 1;
}
message AgentStateRes {
string id = 1;
string state = 2;
}
message StopComputation {
string computation_id = 1;
}
@@ -49,6 +58,8 @@ message ClientStreamMessage {
AgentEvent agent_event = 2;
RunResponse run_res = 3;
StopComputationResponse stopComputationRes = 4;
AgentStateRes agentStateRes = 5;
AttestationResponse vTPMattestationReport = 6;
}
}
@@ -57,9 +68,15 @@ message ServerStreamMessage {
RunReqChunks runReqChunks = 1;
ComputationRunReq runReq = 2;
StopComputation stopComputation = 3;
AgentStateReq agentStateReq = 4;
DisconnectReq disconnectReq = 5;
}
}
message DisconnectReq {
string id = 1;
}
message RunReqChunks {
bytes data = 1;
string id = 2;
@@ -93,11 +110,15 @@ message Algorithm {
message AgentConfig {
string port = 1;
string host = 2;
string cert_file = 3;
string key_file = 4;
string client_ca_file = 5;
string server_ca_file = 6;
string log_level = 7;
bool attested_tls = 8;
string cert_file = 2;
string key_file = 3;
string client_ca_file = 4;
string server_ca_file = 5;
string log_level = 6;
bool attested_tls = 7;
}
message AttestationResponse {
bytes file = 1;
string certSerialNumber = 2;
}
+25 -13
View File
@@ -8,12 +8,12 @@ import (
"fmt"
"log/slog"
"github.com/google/go-sev-guest/client"
"github.com/ultravioletrs/cocos/agent"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
@@ -24,7 +24,7 @@ const (
)
type AgentServer interface {
Start(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation) error
Start(cfg agent.AgentConfig, cmp agent.Computation) error
Stop() error
}
@@ -32,16 +32,24 @@ type agentServer struct {
gs server.Server
logger *slog.Logger
svc agent.Service
host string
qp client.LeveledQuoteProvider
caUrl string
cvmId string
}
func NewServer(logger *slog.Logger, svc agent.Service) AgentServer {
func NewServer(logger *slog.Logger, svc agent.Service, host string, qp client.LeveledQuoteProvider, caUrl string, cvmId string) AgentServer {
return &agentServer{
logger: logger,
svc: svc,
host: host,
qp: qp,
caUrl: caUrl,
cvmId: cvmId,
}
}
func (as *agentServer) Start(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation) error {
func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error {
if cfg.Port == "" {
cfg.Port = defSvcGRPCPort
}
@@ -49,7 +57,7 @@ func (as *agentServer) Start(ctx context.Context, cfg agent.AgentConfig, cmp age
agentGrpcServerConfig := server.AgentConfig{
ServerConfig: server.ServerConfig{
BaseConfig: server.BaseConfig{
Host: cfg.Host,
Host: as.host,
Port: cfg.Port,
CertFile: cfg.CertFile,
KeyFile: cfg.KeyFile,
@@ -71,19 +79,23 @@ func (as *agentServer) Start(ctx context.Context, cfg agent.AgentConfig, cmp age
return err
}
qp, err := quoteprovider.GetQuoteProvider()
if err != nil {
as.logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
return err
}
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(ctx)
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, as.qp, authSvc, as.caUrl, as.cvmId)
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, qp, authSvc)
go func() {
err := as.gs.Start()
if err != nil {
as.logger.Error(fmt.Sprintf("failed to start grpc server %s", err.Error()))
}
}()
return as.gs.Start()
return nil
}
func (as *agentServer) Stop() error {
if as.gs == nil {
return nil
}
return as.gs.Stop()
}
+33 -37
View File
@@ -1,42 +1,39 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
agent "github.com/ultravioletrs/cocos/agent"
mock "github.com/stretchr/testify/mock"
agent "github.com/ultravioletrs/cocos/agent"
)
// AgentServerProvider is an autogenerated mock type for the AgentServerProvider type
type AgentServerProvider struct {
// AgentServer is an autogenerated mock type for the AgentServer type
type AgentServer struct {
mock.Mock
}
type AgentServerProvider_Expecter struct {
type AgentServer_Expecter struct {
mock *mock.Mock
}
func (_m *AgentServerProvider) EXPECT() *AgentServerProvider_Expecter {
return &AgentServerProvider_Expecter{mock: &_m.Mock}
func (_m *AgentServer) EXPECT() *AgentServer_Expecter {
return &AgentServer_Expecter{mock: &_m.Mock}
}
// Start provides a mock function with given fields: ctx, cfg, cmp
func (_m *AgentServerProvider) Start(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation) error {
ret := _m.Called(ctx, cfg, cmp)
// Start provides a mock function with given fields: cfg, cmp
func (_m *AgentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error {
ret := _m.Called(cfg, cmp)
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, agent.AgentConfig, agent.Computation) error); ok {
r0 = rf(ctx, cfg, cmp)
if rf, ok := ret.Get(0).(func(agent.AgentConfig, agent.Computation) error); ok {
r0 = rf(cfg, cmp)
} else {
r0 = ret.Error(0)
}
@@ -44,38 +41,37 @@ func (_m *AgentServerProvider) Start(ctx context.Context, cfg agent.AgentConfig,
return r0
}
// AgentServerProvider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
type AgentServerProvider_Start_Call struct {
// AgentServer_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
type AgentServer_Start_Call struct {
*mock.Call
}
// Start is a helper method to define mock.On call
// - ctx context.Context
// - cfg agent.AgentConfig
// - cmp agent.Computation
func (_e *AgentServerProvider_Expecter) Start(ctx interface{}, cfg interface{}, cmp interface{}) *AgentServerProvider_Start_Call {
return &AgentServerProvider_Start_Call{Call: _e.mock.On("Start", ctx, cfg, cmp)}
func (_e *AgentServer_Expecter) Start(cfg interface{}, cmp interface{}) *AgentServer_Start_Call {
return &AgentServer_Start_Call{Call: _e.mock.On("Start", cfg, cmp)}
}
func (_c *AgentServerProvider_Start_Call) Run(run func(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation)) *AgentServerProvider_Start_Call {
func (_c *AgentServer_Start_Call) Run(run func(cfg agent.AgentConfig, cmp agent.Computation)) *AgentServer_Start_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(agent.AgentConfig), args[2].(agent.Computation))
run(args[0].(agent.AgentConfig), args[1].(agent.Computation))
})
return _c
}
func (_c *AgentServerProvider_Start_Call) Return(_a0 error) *AgentServerProvider_Start_Call {
func (_c *AgentServer_Start_Call) Return(_a0 error) *AgentServer_Start_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentServerProvider_Start_Call) RunAndReturn(run func(context.Context, agent.AgentConfig, agent.Computation) error) *AgentServerProvider_Start_Call {
func (_c *AgentServer_Start_Call) RunAndReturn(run func(agent.AgentConfig, agent.Computation) error) *AgentServer_Start_Call {
_c.Call.Return(run)
return _c
}
// Stop provides a mock function with given fields:
func (_m *AgentServerProvider) Stop() error {
// Stop provides a mock function with no fields
func (_m *AgentServer) Stop() error {
ret := _m.Called()
if len(ret) == 0 {
@@ -92,40 +88,40 @@ func (_m *AgentServerProvider) Stop() error {
return r0
}
// AgentServerProvider_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
type AgentServerProvider_Stop_Call struct {
// AgentServer_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
type AgentServer_Stop_Call struct {
*mock.Call
}
// Stop is a helper method to define mock.On call
func (_e *AgentServerProvider_Expecter) Stop() *AgentServerProvider_Stop_Call {
return &AgentServerProvider_Stop_Call{Call: _e.mock.On("Stop")}
func (_e *AgentServer_Expecter) Stop() *AgentServer_Stop_Call {
return &AgentServer_Stop_Call{Call: _e.mock.On("Stop")}
}
func (_c *AgentServerProvider_Stop_Call) Run(run func()) *AgentServerProvider_Stop_Call {
func (_c *AgentServer_Stop_Call) Run(run func()) *AgentServer_Stop_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *AgentServerProvider_Stop_Call) Return(_a0 error) *AgentServerProvider_Stop_Call {
func (_c *AgentServer_Stop_Call) Return(_a0 error) *AgentServer_Stop_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *AgentServerProvider_Stop_Call) RunAndReturn(run func() error) *AgentServerProvider_Stop_Call {
func (_c *AgentServer_Stop_Call) RunAndReturn(run func() error) *AgentServer_Stop_Call {
_c.Call.Return(run)
return _c
}
// NewAgentServerProvider creates a new instance of AgentServerProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// NewAgentServer creates a new instance of AgentServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewAgentServerProvider(t interface {
func NewAgentServer(t interface {
mock.TestingT
Cleanup(func())
}) *AgentServerProvider {
mock := &AgentServerProvider{}
}) *AgentServer {
mock := &AgentServer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
+7 -7
View File
@@ -3,7 +3,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.0
// protoc-gen-go v1.36.5
// protoc v5.29.0
// source: agent/events/events.proto
@@ -15,6 +15,7 @@ import (
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -260,7 +261,7 @@ func (*EventsLogs_AgentEvent) isEventsLogs_Message() {}
var File_agent_events_events_proto protoreflect.FileDescriptor
var file_agent_events_events_proto_rawDesc = []byte{
var file_agent_events_events_proto_rawDesc = string([]byte{
0x0a, 0x19, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x65,
0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x76, 0x65,
0x6e, 0x74, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
@@ -299,16 +300,16 @@ var file_agent_events_events_proto_rawDesc = []byte{
0x67, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
})
var (
file_agent_events_events_proto_rawDescOnce sync.Once
file_agent_events_events_proto_rawDescData = file_agent_events_events_proto_rawDesc
file_agent_events_events_proto_rawDescData []byte
)
func file_agent_events_events_proto_rawDescGZIP() []byte {
file_agent_events_events_proto_rawDescOnce.Do(func() {
file_agent_events_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_events_events_proto_rawDescData)
file_agent_events_events_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_events_events_proto_rawDesc), len(file_agent_events_events_proto_rawDesc)))
})
return file_agent_events_events_proto_rawDescData
}
@@ -345,7 +346,7 @@ func file_agent_events_events_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_agent_events_events_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_events_events_proto_rawDesc), len(file_agent_events_events_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
@@ -356,7 +357,6 @@ func file_agent_events_events_proto_init() {
MessageInfos: file_agent_events_events_proto_msgTypes,
}.Build()
File_agent_events_events_proto = out.File
file_agent_events_events_proto_rawDesc = nil
file_agent_events_events_proto_goTypes = nil
file_agent_events_events_proto_depIdxs = nil
}
+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
}
+65 -17
View File
@@ -1,14 +1,15 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
import (
context "context"
agent "github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
context "context"
mock "github.com/stretchr/testify/mock"
)
@@ -73,9 +74,9 @@ func (_c *Service_Algo_Call) RunAndReturn(run func(context.Context, agent.Algori
return _c
}
// Attestation provides a mock function with given fields: ctx, reportData
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte, error) {
ret := _m.Called(ctx, reportData)
// Attestation provides a mock function with given fields: ctx, reportData, nonce, attType
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType config.AttestationType) ([]byte, error) {
ret := _m.Called(ctx, reportData, nonce, attType)
if len(ret) == 0 {
panic("no return value specified for Attestation")
@@ -83,19 +84,19 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) ([]byte, error)); ok {
return rf(ctx, reportData)
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)); ok {
return rf(ctx, reportData, nonce, attType)
}
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) []byte); ok {
r0 = rf(ctx, reportData)
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) []byte); ok {
r0 = rf(ctx, reportData, nonce, attType)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, [64]byte) error); ok {
r1 = rf(ctx, reportData)
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, config.AttestationType) error); ok {
r1 = rf(ctx, reportData, nonce, attType)
} else {
r1 = ret.Error(1)
}
@@ -111,13 +112,15 @@ type Service_Attestation_Call struct {
// Attestation is a helper method to define mock.On call
// - ctx context.Context
// - reportData [64]byte
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}) *Service_Attestation_Call {
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData)}
// - nonce [32]byte
// - attType config.AttestationType
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}, nonce interface{}, attType interface{}) *Service_Attestation_Call {
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData, nonce, attType)}
}
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte)) *Service_Attestation_Call {
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, nonce [32]byte, attType config.AttestationType)) *Service_Attestation_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([64]byte))
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(config.AttestationType))
})
return _c
}
@@ -127,7 +130,7 @@ func (_c *Service_Attestation_Call) Return(_a0 []byte, _a1 error) *Service_Attes
return _c
}
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte) ([]byte, error)) *Service_Attestation_Call {
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)) *Service_Attestation_Call {
_c.Call.Return(run)
return _c
}
@@ -284,6 +287,51 @@ func (_c *Service_Result_Call) RunAndReturn(run func(context.Context) ([]byte, e
return _c
}
// State provides a mock function with no fields
func (_m *Service) State() string {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for State")
}
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// Service_State_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'State'
type Service_State_Call struct {
*mock.Call
}
// State is a helper method to define mock.On call
func (_e *Service_Expecter) State() *Service_State_Call {
return &Service_State_Call{Call: _e.mock.On("State")}
}
func (_c *Service_State_Call) Run(run func()) *Service_State_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *Service_State_Call) Return(_a0 string) *Service_State_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Service_State_Call) RunAndReturn(run func() string) *Service_State_Call {
_c.Call.Return(run)
return _c
}
// StopComputation provides a mock function with given fields: ctx
func (_m *Service) StopComputation(ctx context.Context) error {
ret := _m.Called(ctx)
+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) })
+67 -26
View File
@@ -23,6 +23,9 @@ 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/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"golang.org/x/crypto/sha3"
)
@@ -69,8 +72,6 @@ const (
)
const (
// ReportDataSize is the size of the report data expected by the attestation service.
ReportDataSize = 64
algoFilePermission = 0o700
)
@@ -99,6 +100,8 @@ var (
ErrAllResultsConsumed = errors.New("all results have been consumed by declared consumers")
// ErrAttestationFailed attestation failed.
ErrAttestationFailed = errors.New("failed to get raw quote")
// ErrAttType indicates that the attestation type that is requested does not exist or is not supported.
ErrAttestationType = errors.New("attestation type does not exist or is not supported")
)
// Service specifies an API that must be fullfiled by the domain service
@@ -109,27 +112,30 @@ 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 [ReportDataSize]byte) ([]byte, error)
Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]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.QuoteProvider // 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.
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.
vtpmAttest vtpm.VtpmAttest // Attestation function.
}
var _ Service = (*agentService)(nil)
// New instantiates the agent service implementation.
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quoteProvider client.QuoteProvider) Service {
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quoteProvider client.LeveledQuoteProvider, vmlp int, vtpmAttest vtpm.VtpmAttest) Service {
sm := statemachine.NewStateMachine(Idle)
ctx, cancel := context.WithCancel(ctx)
svc := &agentService{
@@ -138,6 +144,8 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quot
quoteProvider: quoteProvider,
logger: logger,
cancel: cancel,
vmpl: vmlp,
vtpmAttest: vtpmAttest,
}
transitions := []statemachine.Transition{
@@ -172,11 +180,15 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quot
return svc
}
func (as *agentService) State() string {
return as.sm.GetState().String()
}
func (as *agentService) InitComputation(ctx context.Context, cmp Computation) error {
defer as.sm.SendEvent(ManifestReceived)
if as.sm.GetState() != ReceivingManifest {
return ErrStateNotReady
}
defer as.sm.SendEvent(ManifestReceived)
as.mu.Lock()
defer as.mu.Unlock()
@@ -203,14 +215,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)
}
@@ -219,13 +233,24 @@ 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())
}
}()
as.sm.SendEvent(Start)
return nil
}
@@ -392,13 +417,29 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
return as.result, as.runError
}
func (as *agentService) Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error) {
rawQuote, err := as.quoteProvider.GetRawQuote(reportData)
if err != nil {
return []byte{}, err
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
switch attType {
case config.SNP:
rawQuote, err := as.quoteProvider.GetRawQuoteAtLevel(reportData, uint(as.vmpl))
if err != nil {
return []byte{}, err
}
return rawQuote, nil
case config.VTPM:
vTPMQuote, err := as.vtpmAttest(reportData[:], nonce[:], false)
if err != nil {
return []byte{}, err
}
return vTPMQuote, nil
case config.SNPvTPM:
vTPMQuote, err := as.vtpmAttest(reportData[:], nonce[:], true)
if err != nil {
return []byte{}, err
}
return vTPMQuote, nil
default:
return []byte{}, ErrAttestationType
}
return rawQuote, nil
}
func (as *agentService) runComputation(state statemachine.State) {
+17 -13
View File
@@ -22,6 +22,7 @@ import (
smmocks "github.com/ultravioletrs/cocos/agent/statemachine/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
mocks2 "github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc/metadata"
)
@@ -35,7 +36,7 @@ var (
const datasetFile = "iris.csv"
func TestAlgo(t *testing.T) {
qp, err := quoteprovider.GetQuoteProvider()
qp, err := quoteprovider.GetLeveledQuoteProvider()
require.NoError(t, err)
algo, err := os.ReadFile(algoPath)
@@ -120,7 +121,7 @@ func TestAlgo(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, qp)
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
err := svc.InitComputation(ctx, testComputation(t))
require.NoError(t, err)
@@ -139,7 +140,7 @@ func TestAlgo(t *testing.T) {
}
func TestData(t *testing.T) {
qp, err := quoteprovider.GetQuoteProvider()
qp, err := quoteprovider.GetLeveledQuoteProvider()
require.NoError(t, err)
algo, err := os.ReadFile(algoPath)
@@ -215,7 +216,7 @@ func TestData(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
svc := New(ctx, mglog.NewMock(), events, qp)
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
err := svc.InitComputation(ctx, testComputation(t))
require.NoError(t, err)
@@ -240,7 +241,7 @@ func TestData(t *testing.T) {
}
func TestResult(t *testing.T) {
qp, err := quoteprovider.GetQuoteProvider()
qp, err := quoteprovider.GetLeveledQuoteProvider()
require.NoError(t, err)
cases := []struct {
@@ -323,23 +324,26 @@ func TestResult(t *testing.T) {
}
func TestAttestation(t *testing.T) {
qp := new(mocks2.QuoteProvider)
qp := new(mocks2.LeveledQuoteProvider)
cases := []struct {
name string
reportData [ReportDataSize]byte
reportData [quoteprovider.Nonce]byte
nonce [vtpm.Nonce]byte
rawQuote []uint8
err error
}{
{
name: "Test attestation successful",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: make([]uint8, 0),
err: nil,
},
{
name: "Test attestation failed",
reportData: generateReportData(),
nonce: [32]byte{},
rawQuote: nil,
err: ErrAttestationFailed,
},
@@ -355,22 +359,22 @@ func TestAttestation(t *testing.T) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
getQuote := qp.On("GetRawQuote", mock.Anything).Return(tc.rawQuote, tc.err)
getQuote := qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.rawQuote, tc.err)
if tc.err != ErrAttestationFailed {
getQuote = qp.On("GetRawQuote", mock.Anything).Return(tc.reportData, nil)
getQuote = qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.nonce, nil)
}
defer getQuote.Unset()
svc := New(ctx, mglog.NewMock(), events, qp)
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
time.Sleep(300 * time.Millisecond)
_, err := svc.Attestation(ctx, tc.reportData)
_, err := svc.Attestation(ctx, tc.reportData, tc.nonce, 0)
assert.True(t, errors.Contains(err, tc.err), "expected %v, got %v", tc.err, err)
})
}
}
func generateReportData() [ReportDataSize]byte {
bytes := make([]byte, ReportDataSize)
func generateReportData() [quoteprovider.Nonce]byte {
bytes := make([]byte, quoteprovider.Nonce)
_, err := rand.Read(bytes)
if err != nil {
log.Fatalf("Failed to generate random bytes: %v", err)
+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
}
+15
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 {
@@ -89,6 +90,20 @@ func (sm *stateMachine) Start(ctx context.Context) error {
}
}
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)
// Create a new event channel
sm.eventChan = make(chan Event)
}
func (sm *stateMachine) handleEvent(event Event) error {
sm.mu.Lock()
currentState := sm.currentState
BIN
View File
Binary file not shown.
+19
View File
@@ -100,3 +100,22 @@ When defining the manifest dataset and algorithm checksums are required. This ca
```bash
./build/cocos-cli checksum <path_to_dataset_or_algorithm>
```
#### Measure IGVM file
We assume that our current working directory is the root of the cocos repository, both on the host machine and in the VM.
`igvmmeasure` calculates the launch measurement for an IGVM file and can generate a signed version. It ensures integrity by precomputing the expected launch digest, which can be verified against the attestation report. The tool parses IGVM directives, outputs the measurement as a hex string, or creates a signed file for verification at guest launch.
##### Example
We measure an IGVM file using our measure command, run:
```bash
./build/cocos-cli igvmmeasure /path/to/igvm/file
```
The tool will parse the directives in the IGVM file, calculate the launch measurement, and output the computed digest. If successful, it prints the measurement to standard output.
Here is a sample output
```
91c4929bec2d0ecf11a708e09f0a57d7d82208bcba2451564444a4b01c22d047995ca27f9053f86de4e8063e9f810548
```
+438 -87
View File
@@ -3,9 +3,11 @@
package cli
import (
"crypto"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"strings"
@@ -17,11 +19,17 @@ import (
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/tools/lib/report"
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm-tools/server"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/ultravioletrs/cocos/agent"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/wrapperspb"
)
@@ -42,9 +50,12 @@ const (
size48 = 48
size64 = 64
attestationFilePath = "attestation.bin"
vtpmFilePath = "../quote.dat"
attestationJson = "attestation.json"
sevProductNameMilan = "Milan"
sevProductNameGenoa = "Genoa"
FormatBinaryPB = "binarypb"
FormatTextProto = "textproto"
exampleJSONConfig = `
{
"rootOfTrust":{
@@ -98,29 +109,47 @@ const (
}
}
`
SNP = "snp"
VTPM = "vtpm"
SNPvTPM = "snp-vtpm"
)
var (
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
cfgString string
timeout time.Duration
maxRetryDelay time.Duration
platformInfo string
stepping string
trustedAuthorKeys []string
trustedAuthorHashes []string
trustedIdKeys []string
trustedIdKeyHashes []string
attestationFile string
attestation []byte
empty16 = [size16]byte{}
empty32 = [size32]byte{}
empty64 = [size64]byte{}
defaultReportIdMa = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
getJsonAttestation bool
errReportSize = errors.New("attestation contents too small")
mode string
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
cfgString string
timeout time.Duration
maxRetryDelay time.Duration
platformInfo string
stepping string
trustedAuthorKeys []string
trustedAuthorHashes []string
trustedIdKeys []string
trustedIdKeyHashes []string
attestationFile string
tpmAttestationFile string
attestation []byte
empty16 = [size16]byte{}
empty32 = [size32]byte{}
empty64 = [size64]byte{}
defaultReportIdMa = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
errReportSize = errors.New("attestation contents too small")
ErrBadAttestation = errors.New("attestation file is corrupted or in wrong format")
output string
nonce []byte
format string
teeNonce []byte
getTextProtoAttestation bool
)
var errEmptyFile = errors.New("input file is empty")
var marshalOptions = prototext.MarshalOptions{
Multiline: true,
EmitASCII: true,
}
var unmarshalOptions = prototext.UnmarshalOptions{}
func (cli *CLI) NewAttestationCmd() *cobra.Command {
return &cobra.Command{
Use: "attestation [command]",
@@ -153,31 +182,75 @@ func (cli *CLI) NewAttestationCmd() *cobra.Command {
func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get",
Short: "Retrieve attestation information from agent. Report data expected in hex enoded string of length 64 bytes.",
Example: "get <report_data>",
Args: cobra.ExactArgs(1),
Use: "get",
Short: "Retrieve attestation information from agent. The argument of the command must be the type of the report (snp or vtpm or snp-vtpm).",
ValidArgs: []cobra.Completion{SNP, VTPM, SNPvTPM},
Example: fmt.Sprintf(`Based on attestation report type:
get %s --tee <512 bit hex value>
get %s --vtpm <256 bit hex value>
get %s --tee <512 bit hex value> --vtpm <256 bit hex value>`, SNP, VTPM, SNPvTPM),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if cli.connectErr != nil {
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
return
}
cmd.Println("Getting attestation")
reportData, err := hex.DecodeString(args[0])
if err != nil {
printError(cmd, "Error decoding report data: %v ❌ ", err)
if err := cobra.OnlyValidArgs(cmd, args); err != nil {
printError(cmd, "Bad attestation type: %v ❌ ", err)
return
}
if len(reportData) != agent.ReportDataSize {
msg := color.New(color.FgRed).Sprintf("report data must be a hex encoded string of length %d bytes ❌ ", agent.ReportDataSize)
attestationType := args[0]
attType := config.SNP
switch attestationType {
case SNP:
cmd.Println("Fetching SEV-SNP attestation report")
case VTPM:
cmd.Println("Fetching vTPM report")
attType = config.VTPM
case SNPvTPM:
cmd.Println("Fetching SEV-SNP and vTPM report")
attType = config.SNPvTPM
}
if (attType == config.VTPM || attType == config.SNPvTPM) && len(nonce) == 0 {
msg := color.New(color.FgRed).Sprint("vTPM nonce must be defined for vTPM attestation ❌ ")
cmd.Println(msg)
return
}
if (attType == config.SNP || attType == config.SNPvTPM) && len(teeNonce) == 0 {
msg := color.New(color.FgRed).Sprint("TEE nonce must be defined for SEV-SNP attestation ❌ ")
cmd.Println(msg)
return
}
var fixedReportData [quoteprovider.Nonce]byte
if attType != config.VTPM {
if len(teeNonce) > quoteprovider.Nonce {
msg := color.New(color.FgRed).Sprintf("nonce must be a hex encoded string of length lesser or equal %d bytes ❌ ", quoteprovider.Nonce)
cmd.Println(msg)
return
}
copy(fixedReportData[:], teeNonce)
}
var fixedVtpmNonceByte [vtpm.Nonce]byte
if attType != config.SNP {
if len(nonce) > vtpm.Nonce {
msg := color.New(color.FgRed).Sprintf("vTPM nonce must be a hex encoded string of length lesser or equal %d bytes ❌ ", vtpm.Nonce)
cmd.Println(msg)
return
}
copy(fixedVtpmNonceByte[:], nonce)
}
filename := attestationFilePath
if getJsonAttestation {
if getTextProtoAttestation {
filename = attestationJson
}
@@ -187,7 +260,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
return
}
if err := cli.agentSDK.Attestation(cmd.Context(), [agent.ReportDataSize]byte(reportData), attestationFile); err != nil {
if err := cli.agentSDK.Attestation(cmd.Context(), fixedReportData, fixedVtpmNonceByte, int(attType), attestationFile); err != nil {
printError(cmd, "Failed to get attestation due to error: %v ❌ ", err)
return
}
@@ -197,16 +270,32 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
return
}
if getJsonAttestation {
if getTextProtoAttestation {
result, err := os.ReadFile(filename)
if err != nil {
printError(cmd, "Error reading attestation file: %v ❌ ", err)
return
}
result, err = attesationToJSON(result)
switch attestationType {
case SNP:
result, err = attesationToJSON(result)
case VTPM, SNPvTPM:
marshalOptions := prototext.MarshalOptions{
Multiline: true,
EmitASCII: true,
}
var attvTPM tpmAttest.Attestation
err = proto.Unmarshal(result, &attvTPM)
if err != nil {
printError(cmd, "failed to unmarshal the attestation report: %v ❌ ", ErrBadAttestation)
}
result = []byte(marshalOptions.Format(&attvTPM))
}
if err != nil {
printError(cmd, "Error converting attestation to json: %v ❌ ", err)
printError(cmd, "Error converting attestation to textproto: %v ❌ ", err)
return
}
@@ -220,7 +309,9 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
},
}
cmd.Flags().BoolVarP(&getJsonAttestation, "json", "j", false, "Get attestation in json format")
cmd.Flags().BoolVarP(&getTextProtoAttestation, "textproto", "p", false, "Get attestation in textproto format")
cmd.Flags().BytesHexVarP(&teeNonce, "tee", "e", []byte{}, "Define the nonce for the SNP attestation report (must be used with attestation type snp and snp-vtpm)")
cmd.Flags().BytesHexVarP(&nonce, "vtpm", "t", []byte{}, "Define the nonce for the vTPM attestation report (must be used with attestation type vtpm and snp-vtpm)")
return cmd
}
@@ -252,52 +343,106 @@ func isFileJSON(filename string) bool {
func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "validate",
Short: "Validate and verify attestation information. The report is provided as a file path.",
Example: "validate <attestation_report_file_path>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cmd.Println("Checking attestation")
Use: "validate",
Short: "Validate and verify attestation information. You can choose from 3 modes: snp,vtpm and snp-vtpm.Default mode is snp.",
Example: `Based on mode:
validate <attestationreportfilepath> --report_data <reportdata> --product <product data> //default
validate --mode snp <attestationreportfilepath> --report_data <reportdata> --product <product data>
validate --mode vtpm <attestationreportfilepath> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>
validate --mode snp-vtpm <attestationreportfilepath> --report_data <reportdata> --product <product data> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>`,
attestationFile = string(args[0])
if err := parseConfig(); err != nil {
printError(cmd, "Error parsing config: %v ❌ ", err)
return
}
if err := parseHashes(); err != nil {
printError(cmd, "Error parsing hashes: %v ❌ ", err)
return
}
if err := parseFiles(); err != nil {
printError(cmd, "Error parsing files: %v ❌ ", err)
return
}
// This format is the attestation report in AMD's specified ABI format, immediately
// followed by the certificate table bytes.
if len(attestation) < abi.ReportSize {
msg := color.New(color.FgRed).Sprintf("attestation contents too small (0x%x bytes). Want at least 0x%x bytes ❌ ", len(attestation), abi.ReportSize)
cmd.Println(msg)
return
}
if err := parseUints(); err != nil {
printError(cmd, "Error parsing uints: %v ❌ ", err)
return
}
cfg.Policy.Vmpl = wrapperspb.UInt32(0)
if err := validateInput(); err != nil {
printError(cmd, "Error validating input: %v ❌ ", err)
return
PreRunE: func(cmd *cobra.Command, args []string) error {
mode, _ := cmd.Flags().GetString("mode")
if len(args) != 1 {
return fmt.Errorf("please pass the attestation report file path")
}
if err := quoteprovider.VerifyAndValidate(attestation, &cfg); err != nil {
printError(cmd, "Attestation validation and verification failed with error: %v ❌ ", err)
return
// Validate flags based on the mode
switch mode {
case "snp":
if err := cmd.MarkFlagRequired("report_data"); err != nil {
return fmt.Errorf("failed to mark 'report_data' as required for SEV-SNP mode: %v", err)
}
if err := cmd.MarkFlagRequired("product"); err != nil {
return fmt.Errorf("failed to mark flag as required: %v ❌ ", err)
}
case "snp-vtpm":
if err := cmd.MarkFlagRequired("nonce"); err != nil {
return fmt.Errorf("failed to mark 'nonce' as required for vTPM mode: %v", err)
}
if err := cmd.MarkFlagRequired("report_data"); err != nil {
return fmt.Errorf("failed to mark 'report_data' as required for SEV-SNP mode: %v", err)
}
if err := cmd.MarkFlagRequired("product"); err != nil {
return fmt.Errorf("failed to mark flag as required: %v ❌ ", err)
}
if err := cmd.MarkFlagRequired("format"); err != nil {
return fmt.Errorf("failed to mark 'format' as required for vTPM mode: %v", err)
}
if err := cmd.MarkFlagRequired("output"); err != nil {
return fmt.Errorf("failed to mark 'output' as required for vTPM mode: %v", err)
}
case "vtpm":
if err := cmd.MarkFlagRequired("nonce"); err != nil {
return fmt.Errorf("failed to mark 'nonce' as required for vTPM mode: %v", err)
}
if err := cmd.MarkFlagRequired("format"); err != nil {
return fmt.Errorf("failed to mark 'format' as required for vTPM mode: %v", err)
}
if err := cmd.MarkFlagRequired("output"); err != nil {
return fmt.Errorf("failed to mark 'output' as required for vTPM mode: %v", err)
}
default:
return fmt.Errorf("unknown mode: %s", mode)
}
cmd.Println("Attestation validation and verification is successful!")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
mode, _ := cmd.Flags().GetString("mode")
switch mode {
case "snp":
return sevsnpverify(cmd, args)
case "snp-vtpm":
return vtpmSevSnpverify(args)
case "vtpm":
return vtpmverify(args)
default:
return fmt.Errorf("unknown mode: %s", mode)
}
},
SilenceUsage: true,
SilenceErrors: true,
}
cmd.Flags().StringVar(
&mode,
"mode",
"snp", // default mode
"The attestation validation mode. Example: sevsnp",
)
// VTPM FLAGS
cmd.Flags().BytesHexVar(
&nonce,
"nonce",
[]byte{},
"hex encoded nonce for vTPM attestation, cannot be empty",
)
cmd.Flags().StringVar(
&format,
"format",
"binarypb", // default value
"type of output file where attestation report stored <binarypb|textproto>",
)
cmd.Flags().StringVar(
&output,
"output",
"",
"output file",
)
// SEV-SNP FLAGS
cmd.Flags().StringVar(
&cfgString,
"config",
@@ -473,19 +618,221 @@ func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
"PEM format CA bundles for the AMD product. Combined with contents of cabundle_paths.",
)
if err := cmd.MarkFlagRequired("report_data"); err != nil {
printError(cmd, "Failed to mark flag as required: %v ❌ ", err)
return nil
}
if err := cmd.MarkFlagRequired("product"); err != nil {
printError(cmd, "Failed to mark flag as required: %v ❌ ", err)
return nil
}
return cmd
}
func (cli *CLI) NewMeasureCmd(igvmBinaryPath string) *cobra.Command {
igvmmeasureCmd := &cobra.Command{
Use: "igvmmeasure <INPUT>",
Short: "Measure an IGVM file",
Long: `igvmmeasure measures an IGVM file and outputs the calculated measurement.
It ensures integrity verification for the IGVM file.`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("error: No input file provided")
}
inputFile := args[0]
measurement, err := cli.measurement.Run(inputFile)
if err != nil {
return err
}
outputString := string(measurement)
lines := strings.Split(strings.TrimSpace(outputString), "\n")
if len(lines) == 1 {
outputString = strings.ToLower(outputString)
} else {
return fmt.Errorf("error: %s", outputString)
}
cmd.Print(outputString)
return nil
},
}
return igvmmeasureCmd
}
func sevsnpverify(cmd *cobra.Command, 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(attestation) < abi.ReportSize {
return fmt.Errorf("attestation too small: got 0x%x bytes, need at least 0x%x bytes", len(attestation), abi.ReportSize)
}
if err := parseAttestationConfig(); err != nil {
return err
}
attestationPB, err := abi.ReportCertsToProto(attestation)
if err != nil {
return fmt.Errorf("failed to convert attestation bytes to struct %v ❌ ", err)
}
if err := quoteprovider.VerifyAndValidate(attestationPB, &cfg); 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) error {
attestation, err := returnvTPMAttestation(args)
if err != nil {
return err
}
var teeAttestation *sevsnp.Attestation
switch attestation.GetTeeAttestation().(type) {
case *tpmAttest.Attestation_SevSnpAttestation:
teeAttestation = attestation.GetSevSnpAttestation()
default:
teeAttestation = nil
}
if teeAttestation == nil {
return fmt.Errorf("tee attestation not fetched")
}
if err := parseAttestationConfig(); err != nil {
return err
}
if err := quoteprovider.VerifyAndValidate(teeAttestation, &cfg); err != nil {
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
}
if err := verifyvTPM(attestation); err != nil {
return err
}
return nil
}
func vtpmverify(args []string) error {
attestation, err := returnvTPMAttestation(args)
if err != nil {
return err
}
if err := verifyvTPM(attestation); err != nil {
return err
}
return nil
}
func verifyvTPM(attestation *tpmAttest.Attestation) error {
pub, err := tpm2.DecodePublic(attestation.GetAkPub())
if err != nil {
return err
}
cryptoPub, err := pub.Key()
if err != nil {
return err
}
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{cryptoPub}, TEEOpts: nil})
if err != nil {
return nil
}
out, err := marshalOptions.Marshal(ms)
if err != nil {
return nil
}
output, err := createOutputFile()
if err != nil {
return err
}
if closer, ok := output.(*os.File); ok {
defer closer.Close()
}
if _, err := output.Write(out); err != nil {
return fmt.Errorf("failed to write verified attestation report: %v", err)
}
return nil
}
func returnvTPMAttestation(args []string) (*tpmAttest.Attestation, error) {
tpmAttestationFile = 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 {
err = proto.Unmarshal(attestationBytes, attestation)
} else if format == FormatTextProto {
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)
}
return attestation, nil
}
func openInputFile() (io.Reader, error) {
if tpmAttestationFile == "" {
return nil, errEmptyFile
}
return os.Open(tpmAttestationFile)
}
func createOutputFile() (io.Writer, error) {
if output == "" {
return os.Stdout, nil
}
return os.Create(output)
}
// parseConfig decodes config passed as json for check.Config struct.
// example
/* {
@@ -574,7 +921,7 @@ func parseHashes() error {
return nil
}
func parseFiles() error {
func parseAttestationFile() error {
file, err := os.ReadFile(attestationFile)
if err != nil {
return err
@@ -587,6 +934,10 @@ func parseFiles() error {
}
}
return nil
}
func parseTrustedKeys() error {
for _, path := range trustedAuthorKeys {
file, err := os.ReadFile(path)
if err != nil {
+138 -7
View File
@@ -3,15 +3,22 @@
package cli
import (
"bytes"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strconv"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"google.golang.org/protobuf/encoding/protojson"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/gcp"
"google.golang.org/protobuf/proto"
)
type fieldType int
@@ -99,6 +106,126 @@ func (cli *CLI) NewAddHostDataCmd() *cobra.Command {
}
}
func (cli *CLI) NewGCPAttestationPolicy() *cobra.Command {
return &cobra.Command{
Use: "gcp",
Short: "Get attestation policy for GCP CVM",
Example: `gcp <bin_vtmp_attestation_report_file> <vcpu_count>`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
attestationBin, err := os.ReadFile(args[0])
if err != nil {
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
return
}
vcpuCount, err := strconv.Atoi(args[1])
if err != nil {
printError(cmd, "Error converting vCPU count to integer: %v ❌ ", err)
return
}
attestation := &attest.Attestation{}
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
return
}
attestationPB := attestation.GetSevSnpAttestation()
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
if err != nil {
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
return
}
launchEndorsement, err := gcp.GetLaunchEndorsement(cmd.Context(), measurement)
if err != nil {
printError(cmd, "Error getting launch endorsement: %v ❌ ", err)
return
}
attestationPolicy, err := gcp.GenerateAttestationPolicy(launchEndorsement, uint32(vcpuCount))
if err != nil {
printError(cmd, "Error generating attestation policy: %v ❌ ", err)
return
}
attestationPolicyJson, err := json.MarshalIndent(attestationPolicy, "", " ")
if err != nil {
printError(cmd, "Error marshaling attestation policy: %v ❌ ", err)
return
}
if err := os.WriteFile("attestation_policy.json", attestationPolicyJson, filePermission); err != nil {
printError(cmd, "Error writing attestation policy file: %v ❌ ", err)
return
}
cmd.Println("Attestation policy file generated successfully ✅")
},
}
}
func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
return &cobra.Command{
Use: "download",
Short: "Download GCP OVMF file",
Example: `download <bin_vtmp_attestation_report_file>`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
attestationBin, err := os.ReadFile(args[0])
if err != nil {
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
return
}
attestation := &attest.Attestation{}
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
return
}
attestationPB := attestation.GetSevSnpAttestation()
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
if err != nil {
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
return
}
launchEndorsement, err := gcp.GetLaunchEndorsement(cmd.Context(), measurement)
if err != nil {
printError(cmd, "Error getting launch endorsement: %v ❌ ", err)
return
}
ovmf, err := gcp.DownloadOvmfFile(cmd.Context(), fmt.Sprintf("%x", launchEndorsement.Digest))
if err != nil {
printError(cmd, "Error downloading OVMF file: %v ❌ ", err)
return
}
sum384 := sha512.Sum384(ovmf)
if !bytes.Equal(sum384[:], launchEndorsement.Digest) {
printError(cmd, "Error OVMF file does not match the measurement: %v ❌ ", fmt.Errorf("digest mismatch"))
} else {
cmd.Println("OVMF firmware in vm is unmodified ✅")
}
if err := os.WriteFile("ovmf.fd", ovmf, filePermission); err != nil {
printError(cmd, "Error writing OVMF file: %v ❌ ", err)
return
}
cmd.Println("OVMF file downloaded successfully ✅")
},
}
}
func changeAttestationConfiguration(fileName, base64Data string, expectedLength int, field fieldType) error {
data, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
@@ -109,27 +236,31 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
return errDataLength
}
ac := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
ac := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
attestationPolicy, err := os.ReadFile(fileName)
f, err := os.ReadFile(fileName)
if err != nil {
return errors.Wrap(errReadingAttestationPolicyFile, err)
}
if err = protojson.Unmarshal(attestationPolicy, &ac); err != nil {
if err = config.ReadAttestationPolicyFromByte(f, &ac); err != nil {
return errors.Wrap(errUnmarshalJSON, err)
}
if ac.Config.Policy == nil {
ac.Config.Policy = &check.Policy{}
}
switch field {
case measurementField:
ac.Policy.Measurement = data
ac.Config.Policy.Measurement = data
case hostDataField:
ac.Policy.HostData = data
ac.Config.Policy.HostData = data
default:
return errAttestationPolicyField
}
fileJson, err := protojson.Marshal(&ac)
fileJson, err := json.MarshalIndent(&ac, "", " ")
if err != nil {
return errors.Wrap(errMarshalJSON, err)
}
+8 -7
View File
@@ -4,13 +4,14 @@ package cli
import (
"encoding/base64"
"encoding/json"
"os"
"testing"
"github.com/google/go-sev-guest/proto/check"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protojson"
config "github.com/ultravioletrs/cocos/pkg/attestation"
)
func TestChangeAttestationConfiguration(t *testing.T) {
@@ -18,9 +19,9 @@ func TestChangeAttestationConfiguration(t *testing.T) {
require.NoError(t, err)
defer os.Remove(tmpfile.Name())
initialConfig := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
initialConfig := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
initialJSON, err := protojson.Marshal(&initialConfig)
initialJSON, err := json.Marshal(initialConfig)
require.NoError(t, err)
err = os.WriteFile(tmpfile.Name(), initialJSON, 0o644)
require.NoError(t, err)
@@ -86,15 +87,15 @@ func TestChangeAttestationConfiguration(t *testing.T) {
content, err := os.ReadFile(tmpfile.Name())
require.NoError(t, err)
config := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
err = protojson.Unmarshal(content, &config)
ap := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
err = config.ReadAttestationPolicyFromByte(content, &ap)
require.NoError(t, err)
decodedData, _ := base64.StdEncoding.DecodeString(tt.base64Data)
if tt.field == measurementField {
assert.Equal(t, decodedData, config.Policy.Measurement)
assert.Equal(t, decodedData, ap.Config.Policy.Measurement)
} else if tt.field == hostDataField {
assert.Equal(t, decodedData, config.Policy.HostData)
assert.Equal(t, decodedData, ap.Config.Policy.HostData)
}
}
})
+208 -30
View File
@@ -14,10 +14,12 @@ import (
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
"github.com/ultravioletrs/cocos/pkg/sdk/mocks"
)
@@ -32,10 +34,8 @@ func TestNewAttestationCmd(t *testing.T) {
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetOutput(&buf)
reportData := bytes.Repeat([]byte{0x01}, agent.ReportDataSize)
mockSDK.On("Attestation", mock.Anything, [agent.ReportDataSize]byte(reportData), mock.Anything).Return(nil)
reportData := bytes.Repeat([]byte{0x01}, quoteprovider.Nonce)
mockSDK.On("Attestation", mock.Anything, [quoteprovider.Nonce]byte(reportData), mock.Anything).Return(nil)
cmd.SetArgs([]string{hex.EncodeToString(reportData)})
err := cmd.Execute()
@@ -46,6 +46,10 @@ func TestNewAttestationCmd(t *testing.T) {
func TestNewGetAttestationCmd(t *testing.T) {
validattestation, err := os.ReadFile("../attestation.bin")
require.NoError(t, err)
teeNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))
vtpmNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
testCases := []struct {
name string
args []string
@@ -55,57 +59,85 @@ func TestNewGetAttestationCmd(t *testing.T) {
expectedOut string
}{
{
name: "successful attestation retrieval",
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
name: "successful SNP attestation retrieval",
args: []string{"snp", "--tee", teeNonce},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedOut: "Attestation result retrieved and saved successfully!",
},
{
name: "invalid report data (decoding error)",
args: []string{"invalid"},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "Error decoding report data",
name: "successful vTPM attestation retrieval",
args: []string{"vtpm", "--vtpm", vtpmNonce},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedOut: "Attestation result retrieved and saved successfully!",
},
{
name: "successful SNP-vTPM attestation retrieval",
args: []string{"snp-vtpm", "--tee", teeNonce, "--vtpm", vtpmNonce},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedOut: "Attestation result retrieved and saved successfully!",
},
{
name: "missing vTPM nonce",
args: []string{"snp-vtpm", "--tee", teeNonce},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedOut: "vTPM nonce must be defined for vTPM attestation",
},
{
name: "missing TEE nonce",
args: []string{"snp-vtpm", "--vtpm", vtpmNonce},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedOut: "TEE nonce must be defined for SEV-SNP attestation",
},
{
name: "invalid report data size",
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, 32))},
args: []string{"snp", "--tee", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 65))},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "report data must be a hex encoded string of length 64 bytes",
expectedErr: "nonce must be a hex encoded string of length lesser or equal 64 bytes",
},
{
name: "invalid report data hex",
name: "invalid vTPM data size",
args: []string{"vtpm", "-t", 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",
},
{
name: "invalid arguments",
args: []string{"invalid"},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "Error decoding report data",
expectedErr: "Bad attestation type: invalid argument ",
},
{
name: "failed to get attestation",
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
args: []string{"snp", "-e", teeNonce},
mockResponse: nil,
mockError: errors.New("error"),
expectedErr: "Failed to get attestation due to error",
},
{
name: "JSON report error",
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), "--json"},
name: "Textproto report error",
args: []string{"snp", "-e", teeNonce, "--textproto"},
mockResponse: []byte("mock attestation"),
mockError: nil,
expectedErr: "Error converting attestation to json",
expectedErr: "Error converting attestation to textproto",
},
{
name: "successful JSON report",
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), "--json"},
name: "successful Textproto report",
args: []string{"snp", "-e", teeNonce, "--textproto"},
mockResponse: validattestation,
mockError: nil,
expectedOut: "Attestation result retrieved and saved successfully!",
},
{
name: "connection error",
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
args: []string{"snp", "-e", teeNonce},
mockResponse: nil,
mockError: errors.New("failed to connect to agent"),
expectedErr: "Failed to connect to agent",
@@ -125,10 +157,10 @@ func TestNewGetAttestationCmd(t *testing.T) {
}
cmd := cli.NewGetAttestationCmd()
var buf bytes.Buffer
cmd.SetOutput(&buf)
cmd.SetOut(&buf)
mockSDK.On("Attestation", mock.Anything, [agent.ReportDataSize]byte(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
_, err := args.Get(2).(*os.File).Write(tc.mockResponse)
mockSDK.On("Attestation", mock.Anything, [quoteprovider.Nonce]byte(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce)), [vtpm.Nonce]byte(bytes.Repeat([]byte{0x00}, vtpm.Nonce)), mock.Anything, mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
_, err := args.Get(4).(*os.File).Write(tc.mockResponse)
require.NoError(t, err)
})
@@ -145,12 +177,12 @@ func TestNewGetAttestationCmd(t *testing.T) {
}
}
func TestNewValidateAttestationValidationCmd(t *testing.T) {
func TestNewValidateAttestationValidationCmdDefaults(t *testing.T) {
cli := &CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
assert.Equal(t, "validate", cmd.Use)
assert.Equal(t, "Validate and verify attestation information. The report is provided as a file path.", cmd.Short)
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)
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())
@@ -162,6 +194,150 @@ func TestNewValidateAttestationValidationCmd(t *testing.T) {
assert.Equal(t, fmt.Sprint(defaultMaxRetryDelay), cmd.Flag("max_retry_delay").Value.String())
}
func TestNewValidateAttestationValidationCmd(t *testing.T) {
cli := &CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
t.Run("missing attestation report file path", func(t *testing.T) {
err := cmd.Execute()
assert.Error(t, err)
assert.Equal(t, "please pass the attestation report file path", err.Error())
})
t.Run("unknown mode", func(t *testing.T) {
cmd.SetArgs([]string{attestationFilePath, "--mode=invalid"})
err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, err.Error(), "unknown mode")
})
t.Run("snp mode with missing flags", func(t *testing.T) {
cmd.SetArgs([]string{attestationFilePath, "--mode=snp"})
err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, err.Error(), "required flag(s) \"product\", \"report_data\" not set")
})
t.Run("vtpm mode with missing flags", func(t *testing.T) {
cmd.SetArgs([]string{vtpmFilePath, "--mode=vtpm"})
err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, err.Error(), "required flag(s) \"format\", \"nonce\", \"output\", \"product\", \"report_data\" not set")
})
t.Run("snp-vtpm mode with missing flags", func(t *testing.T) {
cmd.SetArgs([]string{vtpmFilePath, "--mode=snp-vtpm"})
err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, err.Error(), "required flag(s) \"format\", \"nonce\", \"output\", \"product\", \"report_data\" not set")
})
t.Run("valid snp mode execution", func(t *testing.T) {
cli := CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
cmd.RunE = func(_ *cobra.Command, _ []string) error {
t.Log("Mock RunE executed instead of sevsnpverify")
return nil
}
cmd.SetArgs([]string{
"../attestation.bin",
"--mode=snp",
"--report_data=" +
"11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff" +
"11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff",
"--product=Milan",
})
err := cmd.PreRunE(cmd, []string{"../attestation.bin"})
assert.NoError(t, err)
})
t.Run("valid vtpm mode execution", func(t *testing.T) {
cli := CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
cmd.RunE = func(_ *cobra.Command, _ []string) error {
t.Log("Mock RunE executed instead of vtpmverify")
return nil
}
cmd.SetArgs([]string{vtpmFilePath, "--mode=vtpm", "--nonce=123abc", "--format=binarypb", "--output=some_output"})
err := cmd.PreRunE(cmd, []string{"../quote.dat"})
assert.NoError(t, err)
})
t.Run("valid snp-vtpm mode execution", func(t *testing.T) {
cli := CLI{}
cmd := cli.NewValidateAttestationValidationCmd()
cmd.RunE = func(_ *cobra.Command, _ []string) error {
t.Log("Mock RunE executed instead of vtpmSevSnpverify")
return nil
}
cmd.SetArgs([]string{vtpmFilePath, "--mode=snp-vtpm", "--nonce=123abc", "--format=textproto", "--output=some_output"})
err := cmd.PreRunE(cmd, []string{"../quote.dat"})
assert.NoError(t, err)
})
}
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)
cliInstance.measurement = mockMeasurement
mockMeasurement.On("Run", "testfile.igvm").Return(nil)
cmd := cliInstance.NewMeasureCmd("fake_binary_path")
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"testfile.igvm"})
err := cmd.Execute()
assert.NoError(t, err)
mockMeasurement.AssertExpectations(t)
}
func TestNewMeasureCmd_RunError(t *testing.T) {
cliInstance := &CLI{}
mockMeasurement := new(MockMeasurement)
cliInstance.measurement = mockMeasurement
expectedError := errors.New("mocked measurement error")
mockMeasurement.On("Run", "testfile.igvm").Return(expectedError)
cmd := cliInstance.NewMeasureCmd("fake_binary_path")
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"testfile.igvm"})
err := cmd.Execute()
assert.Error(t, err)
assert.Equal(t, expectedError.Error(), err.Error())
mockMeasurement.AssertExpectations(t)
}
func TestParseConfig(t *testing.T) {
cfgString = ""
err := parseConfig()
@@ -214,7 +390,9 @@ func TestParseFiles(t *testing.T) {
trustedAuthorKeys = []string{authorKeyFile}
trustedIdKeys = []string{idKeyFile}
err = parseFiles()
err = parseAttestationFile()
assert.NoError(t, err)
err = parseTrustedKeys()
assert.NoError(t, err)
assert.Equal(t, []byte("test attestation"), attestation)
assert.Len(t, cfg.Policy.TrustedAuthorKeys, 1)
@@ -225,7 +403,7 @@ func TestParseFiles(t *testing.T) {
os.Remove(idKeyFile)
attestationFile = "non_existent_file.bin"
err = parseFiles()
err = parseAttestationFile()
assert.Error(t, err)
}
+4 -4
View File
@@ -12,7 +12,7 @@ import (
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/verify/trust"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
config "github.com/ultravioletrs/cocos/pkg/attestation"
)
const (
@@ -27,14 +27,14 @@ func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
Example: "ca-bundle <path_to_platform_info_json>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
attestationConfiguration := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
err := grpc.ReadAttestationPolicy(args[0], &attestationConfiguration)
attestationConfiguration := config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
err := config.ReadAttestationPolicy(args[0], &attestationConfiguration)
if err != nil {
printError(cmd, "Error while reading manifest: %v ❌ ", err)
return
}
product := attestationConfiguration.RootOfTrust.ProductLine
product := attestationConfiguration.Config.RootOfTrust.ProductLine
getter := trust.DefaultHTTPSGetter()
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
+19 -3
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 {
@@ -47,6 +52,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 +66,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 +74,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, agentCVMCaUrl, "", "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,7 +89,7 @@ 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 {
@@ -87,7 +103,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
+4 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
"github.com/ultravioletrs/cocos/pkg/clients/grpc/agent"
managergrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/manager"
@@ -22,12 +23,14 @@ type CLI struct {
client grpc.Client
managerClient manager.ManagerServiceClient
connectErr error
measurement cmdconfig.MeasurementProvider
}
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig) *CLI {
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig, measurement cmdconfig.MeasurementProvider) *CLI {
return &CLI{
agentConfig: agentConfig,
managerConfig: managerConfig,
measurement: measurement,
}
}
+122 -14
View File
@@ -4,6 +4,11 @@ package main
import (
"context"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"log"
"log/slog"
@@ -16,16 +21,20 @@ import (
"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"
cvmapi "github.com/ultravioletrs/cocos/agent/cvms/api/grpc"
cvmsapi "github.com/ultravioletrs/cocos/agent/cvms/api/grpc"
"github.com/ultravioletrs/cocos/agent/cvms/server"
"github.com/ultravioletrs/cocos/agent/events"
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
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/vtpm"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
cvmgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
cvmsgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
"golang.org/x/sync/errgroup"
)
@@ -34,10 +43,15 @@ const (
defSvcGRPCPort = "7002"
retryInterval = 5 * time.Second
envPrefixCVMGRPC = "AGENT_CVM_GRPC_"
storageDir = "/var/lib/cocos/agent"
)
type config struct {
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
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:""`
}
func main() {
@@ -71,11 +85,22 @@ func main() {
return
}
qp, err := quoteprovider.GetQuoteProvider()
if err != nil {
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
exitCode = 1
return
var qp client.LeveledQuoteProvider
vtpmAttest := vtpm.Attest
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
vtpmAttest = vtpm.EmptyAttest
} else {
qp, err = quoteprovider.GetLeveledQuoteProvider()
if err != nil {
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
exitCode = 1
return
}
}
cvmGrpcConfig := pkggrpc.CVMClientConfig{}
@@ -85,7 +110,7 @@ func main() {
return
}
cvmGRPCClient, cvmClient, err := cvmgrpc.NewCVMClient(cvmGrpcConfig)
cvmGRPCClient, cvmsClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
if err != nil {
logger.Error(err.Error())
exitCode = 1
@@ -93,16 +118,48 @@ func main() {
}
defer cvmGRPCClient.Close()
pc, err := cvmClient.Process(ctx)
reconnectFn := func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) {
grpcClient, newClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
if err != nil {
return nil, nil, err
}
// Don't defer close here as we want to keep the connection open
pc, err := newClient.Process(ctx)
if err != nil {
grpcClient.Close()
return nil, nil, err
}
return grpcClient, pc, nil
}
pc, err := cvmsClient.Process(ctx)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
svc := newService(ctx, logger, eventSvc, qp)
if cfg.Vmpl < 0 || cfg.Vmpl > 3 {
logger.Error("vmpl level must be in a range [0, 3]")
exitCode = 1
return
}
mc := cvmapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc))
svc := newService(ctx, logger, eventSvc, qp, cfg.Vmpl, vtpmAttest)
if err := os.MkdirAll(storageDir, 0o755); err != nil {
logger.Error(fmt.Sprintf("failed to create storage directory: %s", err))
exitCode = 1
return
}
mc, err := cvmsapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc, cfg.AgentGrpcHost, qp, cfg.CAUrl, cfg.CVMId), storageDir, reconnectFn, cvmGRPCClient)
if err != nil {
logger.Error(err.Error())
exitCode = 1
return
}
g.Go(func() error {
ch := make(chan os.Signal, 1)
@@ -123,13 +180,29 @@ func main() {
return mc.Process(ctx, cancel)
})
attestation, certSerialNumber, err := attestationFromCert(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_VTPMattestationReport{
VTPMattestationReport: &cvms.AttestationResponse{
File: attestation,
CertSerialNumber: certSerialNumber,
},
},
}
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
}
}
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.QuoteProvider) agent.Service {
svc := agent.New(ctx, logger, eventSvc, qp)
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.LeveledQuoteProvider, vmpl int, vtpmAttest vtpm.VtpmAttest) agent.Service {
svc := agent.New(ctx, logger, eventSvc, qp, vmpl, vtpmAttest)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := prometheus.MakeMetrics(svcName, "api")
@@ -137,3 +210,38 @@ 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
}
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
}
nonceSNP := sha512.Sum512(certFile)
nonceVTPM := sha256.Sum256(certFile)
attestation, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestationconfig.SNPvTPM)
if err != nil {
return nil, "", err
}
return attestation, certx509.SerialNumber.String(), nil
}
+15 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/ultravioletrs/cocos/cli"
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
cmd "github.com/virtee/sev-snp-measure-go/sevsnpmeasure/cmd"
)
@@ -28,7 +29,8 @@ const (
)
type config struct {
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
IgvmBinaryPath string `env:"IGVM_BINARY_PATH" envDefault:"./build/igvmmeasure"`
}
func main() {
@@ -106,7 +108,15 @@ func main() {
return
}
cliSVC := cli.New(agentGRPCConfig, managerGRPCConfig)
options := cmdconfig.IgvmMeasureOptions
measurement, err := cmdconfig.NewCmdConfig(cfg.IgvmBinaryPath, options, os.Stderr)
if err != nil {
message := color.New(color.FgRed).Sprintf("failed to initialize measurement: %s", err) // Use %s instead of %w
rootCmd.Println(message)
return
}
cliSVC := cli.New(agentGRPCConfig, managerGRPCConfig, measurement)
if err := cliSVC.InitializeAgentSDK(rootCmd); err == nil {
defer cliSVC.Close()
@@ -136,6 +146,7 @@ func main() {
// measure.
rootCmd.AddCommand(cmd.NewRootCmd())
rootCmd.AddCommand(cliSVC.NewMeasureCmd(cfg.IgvmBinaryPath))
// Flags
keysCmd.PersistentFlags().StringVarP(
@@ -149,6 +160,8 @@ func main() {
// Attestation Policy commands
attestationPolicyCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
attestationPolicyCmd.AddCommand(cliSVC.NewAddHostDataCmd())
attestationPolicyCmd.AddCommand(cliSVC.NewGCPAttestationPolicy())
attestationPolicyCmd.AddCommand(cliSVC.NewDownloadGCPOvmfFile())
if err := rootCmd.Execute(); err != nil {
logErrorCmd(*rootCmd, err)
+7 -5
View File
@@ -42,7 +42,9 @@ type config struct {
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:4318"`
TraceRatio float64 `env:"COCOS_JAEGER_TRACE_RATIO" envDefault:"1.0"`
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build"`
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build/attestation_policy"`
IgvmMeasureBinary string `env:"MANAGER_IGVMMEASURE_BINARY" envDefault:"../../build/igvmmeasure"`
PcrValues string `env:"MANAGER_PCR_VALUES" envDefault:""`
EosVersion string `env:"MANAGER_EOS_VERSION" envDefault:""`
}
@@ -98,7 +100,7 @@ func main() {
return
}
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, 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
@@ -110,7 +112,7 @@ func main() {
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, nil, "", "")
g.Go(func() error {
return gs.Start()
@@ -125,8 +127,8 @@ func main() {
}
}
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, eosVersion string) (manager.Service, error) {
svc, err := manager.New(qemuCfg, attestationPolicyPath, logger, qemu.NewVM, eosVersion)
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, eosVersion string) (manager.Service, error) {
svc, err := manager.New(qemuCfg, attestationPolicyPath, igvmMeasurementBinaryPath, pcrValuesFilePath, logger, qemu.NewVM, eosVersion)
if err != nil {
return nil, err
}
+3 -1
View File
@@ -7,6 +7,8 @@ COCOS_JAEGER_TRACE_RATIO=1.0
# Manager Service Configuration
MANAGER_INSTANCE_ID=
MANAGER_ATTESTATION_POLICY_BINARY=../../build
MANAGER_IGVMMEASURE_BINARY=../../build
MANAGER_PCR_VALUES=/etc/cocos/pcr_values.json
MANAGER_GRPC_CLIENT_CERT=
MANAGER_GRPC_CLIENT_KEY=
MANAGER_GRPC_SERVER_CA_CERTS=
@@ -49,13 +51,13 @@ 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_IGVM_FILE=/etc/cocos/coconut-qemu.igvm
MANAGER_QEMU_ENABLE_KVM=true
MANAGER_QEMU_MACHINE=q35
MANAGER_QEMU_CPU=EPYC
MANAGER_QEMU_SMP_COUNT=4
MANAGER_QEMU_SMP_MAXCPUS=16
MANAGER_QEMU_MEM_ID=ram1
MANAGER_QEMU_KERNEL_HASH=false
MANAGER_QEMU_NO_GRAPHIC=true
MANAGER_QEMU_MONITOR=pty
MANAGER_QEMU_HOST_FWD_RANGE=6100-6200
+74 -27
View File
@@ -1,6 +1,8 @@
module github.com/ultravioletrs/cocos
go 1.23.0
go 1.23.2
toolchain go1.24.1
require (
github.com/absmach/magistrala v0.15.1
@@ -8,30 +10,59 @@ require (
github.com/fatih/color v1.18.0
github.com/go-kit/kit v0.13.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-sev-guest v0.12.1
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/spf13/cobra v1.8.1
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.32.0
golang.org/x/sync v0.10.0
google.golang.org/grpc v1.69.4
google.golang.org/protobuf v1.36.3
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
go.opentelemetry.io/otel/trace v1.35.0
golang.org/x/crypto v0.35.0
golang.org/x/sync v0.12.0
google.golang.org/grpc v1.71.1
google.golang.org/protobuf v1.36.6
)
require (
cloud.google.com/go/storage v1.51.0
github.com/golang/protobuf v1.5.4
github.com/google/gce-tcb-verifier v0.3.1
)
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
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.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/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gofrs/uuid/v5 v5.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/certificate-transparency-go v1.1.2 // 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/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
@@ -39,47 +70,63 @@ require (
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
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.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.32.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.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
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-20250313105043-afbcda9a9963
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/docker v27.5.1+incompatible
github.com/docker/docker v28.0.4+incompatible
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/stdr v1.2.2 // indirect
github.com/google/go-configfs-tsm v0.2.2 // indirect
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
github.com/google/go-tpm v0.9.3
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.23.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // 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.21.1 // 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.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.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
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/virtee/sev-snp-measure-go => github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825
replace github.com/google/go-tpm-tools => github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c
+1357 -61
View File
File diff suppressed because it is too large Load Diff
+143 -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,6 +138,102 @@ 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"
runcmd:
# Create necessary directories
- mkdir -p /cocos
@@ -163,6 +263,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 +306,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."
+5
View File
@@ -71,3 +71,8 @@ CONFIG_NET_9P_VIRTIO=y
CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y
# TCG TPM
CONFIG_TCG_TPM=y
CONFIG_TCG_TPM2_HMAC=y
CONFIG_TCG_PLATFORM=y
+4 -4
View File
@@ -27,14 +27,14 @@ BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
# Linux headers same as kernel
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_11=y
BR2_TOOLCHAIN_HEADERS_LATEST=y
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.12-rc6"
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.11-rc7"
# Kernel
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_GIT=y
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/torvalds/linux.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="v6.12-rc6"
BR2_LINUX_KERNEL_VERSION="v6.12-rc6"
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/coconut-svsm/linux.git"
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="svsm"
BR2_LINUX_KERNEL_VERSION="svsm"
BR2_LINUX_KERNEL_PATCH=""
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/linux.config"
+153 -17
View File
@@ -4,6 +4,7 @@
package grpc
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
@@ -11,20 +12,29 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log/slog"
"math/big"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
certs "github.com/absmach/certs"
certscli "github.com/absmach/certs/cli"
"github.com/absmach/certs/errors"
certssdk "github.com/absmach/certs/sdk"
"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"
@@ -51,16 +61,22 @@ type Server struct {
server.BaseServer
server *grpc.Server
registerService serviceRegister
quoteProvider client.QuoteProvider
quoteProvider client.LeveledQuoteProvider
authSvc auth.Authenticator
health *health.Server
caUrl string
cvmId string
}
type csrReq struct {
CSR string `json:"csr,omitempty"`
}
type serviceRegister func(srv *grpc.Server)
var _ server.Server = (*Server)(nil)
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.QuoteProvider, authSvc auth.Authenticator) server.Server {
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.LeveledQuoteProvider, authSvc auth.Authenticator, caUrl string, cvmId string) server.Server {
base := config.GetBaseConfig()
listenFullAddress := fmt.Sprintf("%s:%s", base.Host, base.Port)
return &Server{
@@ -75,6 +91,8 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser
registerService: registerService,
quoteProvider: qp,
authSvc: authSvc,
caUrl: caUrl,
cvmId: cvmId,
}
}
@@ -94,7 +112,7 @@ func (s *Server) Start() error {
var listener net.Listener
if agCfg, ok := s.Config.(server.AgentConfig); ok && agCfg.AttestedTLS {
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS()
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.caUrl, s.cvmId)
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
@@ -257,33 +275,99 @@ func loadX509KeyPair(certfile, keyfile string) (tls.Certificate, error) {
return tls.X509KeyPair(cert, key)
}
func generateCertificatesForATLS() ([]byte, []byte, error) {
func generateCertificatesForATLS(caUrl string, cvmId string) ([]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{
var certDERBytes []byte
if caUrl == "" || cvmId == "" {
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,
}
DERBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
}
certDERBytes = DERBytes
} else {
csrmd := certs.CSRMetadata{
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)
csr, err := certscli.CreateCSR(csrmd, privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to create CSR: %w", err)
}
csrData := string(csr.CSR)
r := csrReq{
CSR: csrData,
}
data, error := json.Marshal(r)
if error != nil {
return nil, nil, errors.NewSDKError(error)
}
notBefore := time.Now()
notAfter := time.Now().AddDate(notAfterYear, notAfterMonth, notAfterDay)
ttlString := notAfter.Sub(notBefore).String()
query := url.Values{}
query.Add("ttl", ttlString)
query_string := query.Encode()
certsEndpoint := "certs"
csrEndpoint := "csrs"
endpoint := fmt.Sprintf("%s/%s/%s", certsEndpoint, csrEndpoint, cvmId)
url := fmt.Sprintf("%s/%s?%s", caUrl, endpoint, query_string)
_, body, sdkerr := processRequest(http.MethodPost, url, data, nil, http.StatusOK)
if sdkerr != nil {
return nil, nil, errors.NewSDKError(sdkerr)
}
var cert certssdk.Certificate
if err := json.Unmarshal(body, &cert); err != nil {
return nil, nil, errors.NewSDKError(err)
}
cleanCertificateString := strings.ReplaceAll(cert.Certificate, "\\n", "\n")
block, rest := pem.Decode([]byte(cleanCertificateString))
if len(rest) != 0 {
return nil, nil, fmt.Errorf("failed to convert generated certificate to DER format: %s", cleanCertificateString)
}
certDERBytes = block.Bytes
}
certBytes := pem.EncodeToMemory(&pem.Block{
@@ -301,5 +385,57 @@ func generateCertificatesForATLS() ([]byte, []byte, error) {
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
}
func processRequest(method, reqUrl string, data []byte, headers map[string]string, expectedRespCodes ...int) (http.Header, []byte, errors.SDKError) {
req, err := http.NewRequest(method, reqUrl, bytes.NewReader(data))
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
// Sets a default value for the Content-Type.
// Overridden if Content-Type is passed in the headers arguments.
req.Header.Add("Content-Type", "application/json")
for key, value := range headers {
req.Header.Add(key, value)
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
resp, err := client.Do(req)
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
defer resp.Body.Close()
sdkerr := errors.CheckError(resp, expectedRespCodes...)
if sdkerr != nil {
return make(http.Header), []byte{}, sdkerr
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
return resp.Header, body, nil
}
+35 -10
View File
@@ -22,6 +22,7 @@ import (
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"
)
@@ -30,6 +31,28 @@ 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)
}
@@ -47,10 +70,10 @@ func TestNew(t *testing.T) {
},
}
logger := slog.Default()
qp := new(mocks.QuoteProvider)
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, qp, authSvc, "", "")
assert.NotNil(t, srv)
assert.IsType(t, &Server{}, srv)
@@ -97,10 +120,10 @@ func TestServerStartWithTLSFile(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
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, qp, authSvc, "", "")
var wg sync.WaitGroup
wg.Add(1)
@@ -144,10 +167,10 @@ func TestServerStartWithmTLSFile(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
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, qp, authSvc, "", "")
var wg sync.WaitGroup
wg.Add(1)
@@ -184,10 +207,10 @@ func TestServerStop(t *testing.T) {
}
buf := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
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, qp, authSvc, "", "")
go func() {
err := srv.Start()
@@ -259,6 +282,8 @@ func (b *ThreadSafeBuffer) String() string {
}
func TestServerInitializationAndStartup(t *testing.T) {
vtpm.ExternalTPM = &DummyRWC{}
testCases := []struct {
name string
config server.AgentConfig
@@ -374,10 +399,10 @@ func TestServerInitializationAndStartup(t *testing.T) {
logBuffer := &ThreadSafeBuffer{}
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
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, qp, 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()
+108 -126
View File
@@ -6,62 +6,66 @@ Manager service provides a barebones gRPC API and Service interface implementati
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 |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| COCOS_JAEGER_URL | The URL for the Jaeger tracing endpoint. | http://localhost:4318 |
| COCOS_JAEGER_TRACE_RATIO | The ratio of traces to sample. | 1.0 |
| MANAGER_INSTANCE_ID | The instance ID for the manager service. | |
| MANAGER_ATTESTATION_POLICY_BINARY | The file path for the attestation policy binary. | ../../build |
| MANAGER_GRPC_CLIENT_CERT | The file path for the client certificate. | |
| MANAGER_GRPC_CLIENT_KEY | The file path for the client private key. | |
| 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_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 |
| MANAGER_QEMU_MAX_MEMORY | The maximum memory size for the virtual machine. Can be specified in a human-readable format like "30G". | 30G |
| MANAGER_QEMU_OVMF_CODE_IF | The interface type for the OVMF code. | pflash |
| MANAGER_QEMU_OVMF_CODE_FORMAT | The format of the OVMF code file. | raw |
| MANAGER_QEMU_OVMF_CODE_UNIT | The unit number for the OVMF code. | 0 |
| MANAGER_QEMU_OVMF_CODE_FILE | The file path for the OVMF code. | /usr/share/OVMF/OVMF_CODE.fd |
| MANAGER_QEMU_OVMF_VERSION | The version number of EDKII from which OVMF was built | edk2-stable202408 |
| MANAGER_QEMU_OVMF_CODE_READONLY | Whether the OVMF code should be read-only. | on |
| MANAGER_QEMU_OVMF_VARS_IF | The interface type for the OVMF variables. | pflash |
| MANAGER_QEMU_OVMF_VARS_FORMAT | The format of the OVMF variables file. | raw |
| MANAGER_QEMU_OVMF_VARS_UNIT | The unit number for the OVMF variables. | 1 |
| MANAGER_QEMU_OVMF_VARS_FILE | The file path for the OVMF variables. | /usr/share/OVMF/OVMF_VARS.fd |
| MANAGER_QEMU_NETDEV_ID | The ID for the network device. | vmnic |
| MANAGER_QEMU_HOST_FWD_AGENT | The port number for the host forward agent. | 7020 |
| MANAGER_QEMU_GUEST_FWD_AGENT | The port number for the guest forward agent. | 7002 |
| MANAGER_QEMU_VIRTIO_NET_PCI_DISABLE_LEGACY | Whether to disable the legacy PCI device. | on |
| MANAGER_QEMU_VIRTIO_NET_PCI_IOMMU_PLATFORM | Whether to enable the IOMMU platform for the virtio-net PCI device. | true |
| MANAGER_QEMU_VIRTIO_NET_PCI_ADDR | The PCI address for the virtio-net PCI device. | 0x2 |
| 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_HOST_DATA | Additional data for the SEV host. | |
| MANAGER_QEMU_VSOCK_ID | The ID for the virtual socket device. | vhost-vsock-pci0 |
| MANAGER_QEMU_VSOCK_GUEST_CID | The guest-side CID (Context ID) for the virtual socket device. | 3 |
| 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_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 |
| MANAGER_QEMU_SMP_COUNT | The number of virtual CPUs. | 4 |
| MANAGER_QEMU_SMP_MAXCPUS | The maximum number of virtual CPUs. | 64 |
| MANAGER_QEMU_MEM_ID | The ID for the memory device. | ram1 |
| MANAGER_QEMU_KERNEL_HASH | Whether to enable kernel hash verification. | false |
| MANAGER_QEMU_NO_GRAPHIC | Whether to disable the graphical display. | true |
| MANAGER_QEMU_MONITOR | The type of monitor to use. | pty |
| MANAGER_QEMU_HOST_FWD_RANGE | The range of host ports to forward. | 6100-6200 |
| Variable | Description | Default |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| COCOS_JAEGER_URL | The URL for the Jaeger tracing endpoint. | http://localhost:4318 |
| COCOS_JAEGER_TRACE_RATIO | The ratio of traces to sample. | 1.0 |
| MANAGER_INSTANCE_ID | The instance ID for the manager service. | |
| MANAGER_ATTESTATION_POLICY_BINARY | The file path for the attestation policy binarie. | ../../build/attestation_policy |
| MANAGER_IGVMMEASURE_BINARY | The file path for the igvmmeasure binarie. | ../../build/igvmmeasure |
| MANAGER_PCR_VALUES | The file path for the file with the expected PCR values. | |
| MANAGER_GRPC_CLIENT_CERT | The file path for the client certificate. | |
| MANAGER_GRPC_CLIENT_KEY | The file path for the client private key. | |
| 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 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 |
| MANAGER_QEMU_MAX_MEMORY | The maximum memory size for the virtual machine. Can be specified in a human-readable format like "30G". | 30G |
| MANAGER_QEMU_OVMF_CODE_IF | The interface type for the OVMF code. | pflash |
| MANAGER_QEMU_OVMF_CODE_FORMAT | The format of the OVMF code file. | raw |
| MANAGER_QEMU_OVMF_CODE_UNIT | The unit number for the OVMF code. | 0 |
| MANAGER_QEMU_OVMF_CODE_FILE | The file path for the OVMF code. | /usr/share/OVMF/OVMF_CODE.fd |
| MANAGER_QEMU_OVMF_VERSION | The version number of EDKII from which OVMF was built | edk2-stable202408 |
| MANAGER_QEMU_OVMF_CODE_READONLY | Whether the OVMF code should be read-only. | on |
| MANAGER_QEMU_OVMF_VARS_IF | The interface type for the OVMF variables. | pflash |
| MANAGER_QEMU_OVMF_VARS_FORMAT | The format of the OVMF variables file. | raw |
| MANAGER_QEMU_OVMF_VARS_UNIT | The unit number for the OVMF variables. | 1 |
| MANAGER_QEMU_OVMF_VARS_FILE | The file path for the OVMF variables. | /usr/share/OVMF/OVMF_VARS.fd |
| MANAGER_QEMU_NETDEV_ID | The ID for the network device. | vmnic |
| MANAGER_QEMU_HOST_FWD_AGENT | The port number for the host forward agent. | 7020 |
| MANAGER_QEMU_GUEST_FWD_AGENT | The port number for the guest forward agent. | 7002 |
| MANAGER_QEMU_VIRTIO_NET_PCI_DISABLE_LEGACY | Whether to disable the legacy PCI device. | on |
| MANAGER_QEMU_VIRTIO_NET_PCI_IOMMU_PLATFORM | Whether to enable the IOMMU platform for the virtio-net PCI device. | true |
| MANAGER_QEMU_VIRTIO_NET_PCI_ADDR | The PCI address for the virtio-net PCI device. | 0x2 |
| 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_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 |
| MANAGER_QEMU_VSOCK_GUEST_CID | The guest-side CID (Context ID) for the virtual socket device. | 3 |
| 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_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 |
| MANAGER_QEMU_SMP_COUNT | The number of virtual CPUs. | 4 |
| MANAGER_QEMU_SMP_MAXCPUS | The maximum number of virtual CPUs. | 64 |
| MANAGER_QEMU_MEM_ID | The ID for the memory device. | ram1 |
| MANAGER_QEMU_NO_GRAPHIC | Whether to disable the graphical display. | true |
| MANAGER_QEMU_MONITOR | The type of monitor to use. | pty |
| MANAGER_QEMU_HOST_FWD_RANGE | The range of host ports to forward. | 6100-6200 |
## Setup
@@ -88,21 +92,11 @@ sudo apt install qemu-kvm
Create `img` directory in `cmd/manager`.
#### Add Vsock
The necessary kernel modules must be loaded on the hypervisor. To check if `vhost_vsock` is loaded run:
```shell
lsmod | grep vhost_vsock
```
#### Virtual filesystem
If `vhost_vsock` is not loaded run the following commands:
9P (or Plan 9 Filesystem) in QEMU is a lightweight, network-based file-sharing protocol. In Cocos, the 9P is used to transfer environment variables and TLS certificates for cloud communication from the Manager to the Agent.
```shell
sudo modprobe vhost_vsock
ls -l /dev/vhost-vsock
# crw-rw-rw- 1 root kvm 10, 241 Jan 16 12:05 /dev/vhost-vsock
ls -l /dev/vsock
# crw-rw-rw- 1 root root 10, 121 Jan 16 12:05 /dev/vsock
```
You should define the environment variables in a file called environment. For the number and meaning of the environment variables, please refer to the Agent [Readme](https://github.com/ultravioletrs/cocos/blob/main/agent/README.md).
### Prepare Cocos HAL
@@ -122,10 +116,32 @@ OVMF_CODE=/usr/share/OVMF/OVMF_CODE.fd
sudo find / -name OVMF_VARS.fd
# => /usr/share/OVMF/OVMF_VARS.fd
OVMF_VARS=/usr/share/OVMF/OVMF_VARS.fd
# Create a local copy of OVMF_VARS.
cp /usr/share/OVMF/OVMF_VARS.fd .
# Create a directory for the environment file and the certificates for cloud certificates.
mkdir env
mkdir certs
# Enter the env directory and create the environemnt file.
cd env
touch environment
# Define Computations endpoint URL for agent.
# Make sure the Computation endpoint is running (like Cocos Prism).
echo AGENT_CVM_GRPC_URL=localhost:7001 >> ./environment
# Define log level for the agent.
echo AGENT_LOG_LEVEL=debug >> ./environment
# Return to cmd/manager
cd ..
OVMF_VARS=./OVMF_VARS.fd
KERNEL="img/bzImage"
INITRD="img/rootfs.cpio.gz"
ENV_PATH=./env
CERTH_PATH=./certs
qemu-system-x86_64 \
-enable-kvm \
@@ -135,15 +151,19 @@ qemu-system-x86_64 \
-m 2048M,slots=5,maxmem=10240M \
-no-reboot \
-drive if=pflash,format=raw,unit=0,file=$OVMF_CODE,readonly=on \
-drive if=pflash,format=raw,unit=1,file=$OVMF_VARS \
-netdev user,id=vmnic,hostfwd=tcp::7020-:7002 \
-device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= \
-device vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=3 -vnc :0 \
-kernel $KERNEL \
-append "earlyprintk=serial console=ttyS0" \
-initrd $INITRD \
-nographic \
-monitor pty \
-monitor unix:monitor,server,nowait
-monitor unix:monitor,server,nowait \
-fsdev local,id=env_fs,path=$ENV_PATH,security_model=mapped \
-device virtio-9p-pci,fsdev=env_fs,mount_tag=env_share \
-fsdev local,id=cert_fs,path=$CERTH_PATH,security_model=mapped \
-device virtio-9p-pci,fsdev=cert_fs,mount_tag=certs_share
```
Once the VM is booted press enter and on the login use username `root`.
@@ -188,10 +208,21 @@ MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/OVMF/OVMF_VARS.fd
NB: we set environment variables that we will use in the shell process where we run `manager`.
### Trusted Platform Module (TPM)
The Trusted Platform Module (TPM) plays a fundamental role in this process by providing a tamper-resistant foundation for cryptographic operations, securing sensitive artifacts, measuring system state, and enabling attestation mechanisms.
### IGVM
An IGVM file contains all the necessary information to launch a virtual machine on different virtualization platforms. It includes setup commands for the guest system and verification data to ensure the VM is loaded securely and correctly.
Cocos uses the [COCONUT-SVSM](https://github.com/coconut-svsm/svsm/blob/main/Documentation/docs/installation/INSTALL.md) for the vTPM. The IGVM file contains the OVMF file and the vTPM.
## Deployment
To start the service, execute the following shell script (note a server needs to be running see [here](../test/computations/README.md)):
To start the service, execute the following shell script (note a server needs to be running see [here](../test/cvms/README.md)):
The manager can be started as a *systemd* service or a standalone executable. To start the manager as a systemd service, look at the systemd service script [here](https://github.com/ultravioletrs/cocos/blob/main/init/systemd/cocos-manager.service). The environment variables are defined in the `cocos-manager.env` file. Below are examples of how to start the manager.
```bash
# Download the latest version of the service
@@ -203,13 +234,14 @@ cd cocos
make manager
# Set the environment variables and run the service
MANAGER_GRPC_URL=localhost:7001
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
@@ -221,7 +253,7 @@ MANAGER_QEMU_SEV_CBITPOS=51 \
./build/cocos-manager
```
To build the OVMF with the kernel hash capability, we must build the AmdSev package of OVMF. The result of the build should be a single `OVMF.fd` file (unlike the regular two OVFM files). The OVMF package is located at `OvmfPkg/AmdSev/AmdSevX64.dsc`.
To start SEV-SNP, define the IGVM file that contains the vTPM and the OVMF (combined OVMF_CODE and OVMF_VARS) of the CVM.
To enable [AMD SEV-SNP](https://www.amd.com/en/developer/sev.html) support, start manager like this
@@ -232,60 +264,10 @@ MANAGER_QEMU_ENABLE_SEV=false \
MANAGER_QEMU_ENABLE_SEV_SNP=true \
MANAGER_QEMU_SEV_CBITPOS=51 \
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
MANAGER_QEMU_IGVM_FILE=<path to IGVM file> \
./build/cocos-manager
```
To include the kernel hash into the measurement of the attestation report (SEV or SEV-SNP), start manager like this
```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_KERNEL_HASH=true \
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
./build/cocos-manager
```
### Verifying VM launch
NB: To verify that the manager successfully launched the VM, you need to open three terminals on the same machine. In one terminal, you need to launch the computations server by executing (with the environment variables of choice):
```bash
go run ./test/computations/main.go <dataset path> <algo path>
```
and in the second the manager by executing (with the environment variables of choice):
```bash
go run ./cmd/manager/main.go
```
Ensure that the Manager can connect to the Manager test server by setting the MANAGER_GRPC_PORT with the port value of the Manager test server. In the last terminal, you can run the verification commands.
To verify that the manager launched the VM successfully, run the following command:
```sh
ps aux | grep qemu-system-x86_64
```
You should get something similar to this
```
darko 324763 95.3 6.0 6398136 981044 ? Sl 16:17 0:15 /usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -nographic -monitor pty
```
If you run a command as `sudo`, you should get the output similar to this one
```
root 37982 0.0 0.0 9444 4572 pts/0 S+ 16:18 0:00 sudo /usr/local/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1 -machine memory-encryption=sev0 -nographic -monitor pty
root 37989 122 13.1 5345816 4252312 pts/0 Sl+ 16:19 0:04 /usr/local/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1 -machine memory-encryption=sev0 -nographic -monitor pty
```
The two processes are due to the fact that we run the command `/usr/bin/qemu-system-x86_64` as `sudo`, so there is one process for `sudo` command and the other for `/usr/bin/qemu-system-x86_64`.
### 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,
+3 -3
View File
@@ -67,11 +67,11 @@ 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)
}
+4 -4
View File
@@ -59,11 +59,11 @@ 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)
}
+55 -21
View File
@@ -7,25 +7,42 @@
package manager
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"os/exec"
"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/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"
"google.golang.org/protobuf/encoding/protojson"
)
const defGuestFeatures = 0x1
func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationId string) ([]byte, error) {
cmd := exec.Command("sudo", fmt.Sprintf("%s/attestation_policy", ms.attestationPolicyBinaryPath), "--policy", "196608")
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 err != nil {
return nil, err
}
ms.mu.Lock()
vm, exists := ms.vms[computationId]
@@ -40,22 +57,15 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
}
ms.ap.Lock()
_, err := cmd.Output()
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
ms.ap.Unlock()
if err != nil {
return nil, err
}
ms.ap.Lock()
f, err := os.ReadFile("./attestation_policy.json")
ms.ap.Unlock()
if err != nil {
return nil, err
}
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
var attestationPolicy check.Config
if err = protojson.Unmarshal(f, &attestationPolicy); err != nil {
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
return nil, err
}
@@ -67,26 +77,50 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
return nil, err
}
case vmi.Config.EnableSEVSNP:
measurement, err = guest.CalcLaunchDigest(guest.SEV_SNP, vmi.Config.SMPCount, uint64(cpuid.CpuSigs[vmi.Config.CPU]), vmi.Config.OVMFCodeConfig.File, vmi.Config.KernelFile, vmi.Config.RootFsFile, strconv.Quote(qemu.KernelCommandLine), defGuestFeatures, "", vmmtypes.QEMU, false, "", 0)
stderr := bufio.NewWriter(&stderrBuffer)
options := cmdconfig.IgvmMeasureOptions
igvmMeasurement, err := cmdconfig.NewCmdConfig(ms.igvmMeasurementBinaryPath, options, stderr)
if err != nil {
return nil, err
}
outputByte, err := igvmMeasurement.Run(ms.qemuCfg.IGVMConfig.File)
if err != nil {
return nil, err
}
outputString := string(outputByte)
lines := strings.Split(strings.TrimSpace(outputString), "\n")
if len(lines) == 1 {
outputString = strings.TrimSpace(outputString)
outputString = strings.ToLower(outputString)
} else {
return nil, fmt.Errorf("error: %s", outputString)
}
measurement, err = hex.DecodeString(outputString)
if err != nil {
return nil, err
}
}
if measurement != nil {
attestationPolicy.Policy.Measurement = measurement
attestationPolicy.Config.Policy.Measurement = measurement
}
if vmi.Config.HostData != "" {
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.HostData)
if vmi.Config.SevConfig.EnableHostData {
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SevConfig.HostData)
if err != nil {
return nil, err
}
attestationPolicy.Policy.HostData = hostData
attestationPolicy.Config.Policy.HostData = hostData
}
attestationPolicy.Policy.MinimumLaunchTcb = vmi.LaunchTCB
attestationPolicy.Config.Policy.MinimumLaunchTcb = vmi.LaunchTCB
f, err = protojson.Marshal(&attestationPolicy)
f, err := json.MarshalIndent(attestationPolicy, "", " ")
if err != nil {
return nil, err
}
+9 -23
View File
@@ -6,6 +6,7 @@ import (
"context"
"encoding/json"
"os"
"path"
"path/filepath"
"testing"
@@ -20,7 +21,7 @@ func CreateDummyAttestationPolicyBinary(t *testing.T, behavior string) string {
switch behavior {
case "success":
content = []byte(`#!/bin/sh
echo '{"policy": {"measurement": null, "host_data": null}}' > attestation_policy.json
echo '{"pcr_values": {"sha256": null, "sha384": null}, "policy": {"measurement": null, "host_data": null}}'
`)
case "fail":
content = []byte(`#!/bin/sh
@@ -57,9 +58,10 @@ func TestFetchAttestationPolicy(t *testing.T) {
binaryBehavior: "success",
vmConfig: qemu.VMInfo{
Config: qemu.Config{
EnableSEV: true,
SMPCount: 2,
CPU: "EPYC",
EnableSEV: true,
EnableSEVSNP: false,
SMPCount: 2,
CPU: "EPYC",
OVMFCodeConfig: qemu.OVMFCodeConfig{
File: "/path/to/OVMF_CODE.fd",
},
@@ -68,23 +70,6 @@ func TestFetchAttestationPolicy(t *testing.T) {
},
expectedError: "open /path/to/OVMF_CODE.fd: no such file or directory",
},
{
name: "Valid SEV-SNP configuration",
computationId: "sev-snp-computation",
binaryBehavior: "success",
vmConfig: qemu.VMInfo{
Config: qemu.Config{
EnableSEVSNP: true,
SMPCount: 4,
CPU: "EPYC-v2",
OVMFCodeConfig: qemu.OVMFCodeConfig{
File: "/path/to/OVMF_CODE_SNP.fd",
},
},
LaunchTCB: 0,
},
expectedError: "open /path/to/OVMF_CODE_SNP.fd: no such file or director",
},
{
name: "Invalid computation ID",
computationId: "non-existent",
@@ -121,7 +106,7 @@ func TestFetchAttestationPolicy(t *testing.T) {
},
LaunchTCB: 0,
},
expectedError: "no such file or directory",
expectedError: "failed to decode Attestation Policy file",
},
}
@@ -132,7 +117,8 @@ func TestFetchAttestationPolicy(t *testing.T) {
ms := &managerService{
vms: make(map[string]vm.VM),
attestationPolicyBinaryPath: tempDir,
attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"),
pcrValuesFilePath: tempDir,
qemuCfg: qemu.Config{
CPU: "EPYC",
},
+105 -85
View File
@@ -3,7 +3,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.0
// protoc-gen-go v1.36.5
// protoc v5.29.0
// source: manager/manager.proto
@@ -15,6 +15,7 @@ import (
emptypb "google.golang.org/protobuf/types/known/emptypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -31,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
}
@@ -100,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
}
@@ -145,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
}
@@ -189,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 ""
}
@@ -248,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"`
@@ -260,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))
@@ -285,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
}
@@ -376,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))
@@ -408,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
}
@@ -422,11 +439,11 @@ func (x *SVMInfoReq) GetId() string {
var File_manager_manager_proto protoreflect.FileDescriptor
var file_manager_manager_proto_rawDesc = []byte{
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,
@@ -443,61 +460,65 @@ var file_manager_manager_proto_rawDesc = []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 (
file_manager_manager_proto_rawDescOnce sync.Once
file_manager_manager_proto_rawDescData = file_manager_manager_proto_rawDesc
file_manager_manager_proto_rawDescData []byte
)
func file_manager_manager_proto_rawDescGZIP() []byte {
file_manager_manager_proto_rawDescOnce.Do(func() {
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(file_manager_manager_proto_rawDescData)
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)))
})
return file_manager_manager_proto_rawDescData
}
@@ -508,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
@@ -538,7 +559,7 @@ func file_manager_manager_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_manager_manager_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)),
NumEnums: 0,
NumMessages: 7,
NumExtensions: 0,
@@ -549,7 +570,6 @@ func file_manager_manager_proto_init() {
MessageInfos: file_manager_manager_proto_msgTypes,
}.Build()
File_manager_manager_proto = out.File
file_manager_manager_proto_rawDesc = nil
file_manager_manager_proto_goTypes = nil
file_manager_manager_proto_depIdxs = nil
}
+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 {
+12 -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,30 @@ 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
}
+20 -15
View File
@@ -56,7 +56,13 @@ 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"`
HostData string `env:"HOST_DATA" envDefault:""`
EnableHostData bool `env:"ENABLE_HOST_DATA" envDefault:"false"`
HostData string `env:"HOST_DATA" envDefault:""`
}
type IGVMConfig struct {
ID string `env:"IGVM_ID" envDefault:"igvm0"`
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
}
type VSockConfig struct {
@@ -80,9 +86,6 @@ type Config struct {
MemID string `env:"MEM_ID" envDefault:"ram1"`
MemoryConfig
// Kernel hash
KernelHash bool `env:"KERNEL_HASH" envDefault:"false"`
// OVMF
OVMFCodeConfig
OVMFVarsConfig
@@ -100,6 +103,9 @@ type Config struct {
// SEV
SevConfig
// vTPM
IGVMConfig
// display
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
Monitor string `env:"MONITOR" envDefault:"pty"`
@@ -173,40 +179,39 @@ func (config Config) ConstructQemuArgs() []string {
// SEV
if config.EnableSEV || config.EnableSEVSNP {
sevType := "sev-guest"
kernelHash := ""
hostData := ""
args = append(args, "-machine",
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s",
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,igvm-cfg=%s",
config.SevConfig.ID,
config.MemID))
config.MemID,
config.IGVMConfig.ID))
if config.EnableSEVSNP {
args = append(args, "-bios", config.OVMFCodeConfig.File)
sevType = "sev-snp-guest"
if config.SevConfig.HostData != "" {
if config.SevConfig.EnableHostData {
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
}
}
if config.KernelHash {
kernelHash = ",kernel-hashes=on"
}
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, "-object",
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s%s",
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
sevType,
config.SevConfig.ID,
config.SevConfig.CBitPos,
config.SevConfig.ReducedPhysBits,
kernelHash,
hostData))
args = append(args, "-object",
fmt.Sprintf("igvm-cfg,id=%s,file=%s",
config.IGVMConfig.ID,
config.IGVMConfig.File))
}
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
+7 -33
View File
@@ -132,6 +132,10 @@ func TestConstructQemuArgs(t *testing.T) {
CBitPos: 51,
ReducedPhysBits: 1,
},
IGVMConfig: IGVMConfig{
ID: "igvm0",
File: "/test/path/cocos-igvm.igvm",
},
NoGraphic: true,
Monitor: "pty",
},
@@ -144,10 +148,10 @@ func TestConstructQemuArgs(t *testing.T) {
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
"-device", "vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=3",
"-machine", "confidential-guest-support=sev0,memory-backend=ram1",
"-bios", "/usr/share/OVMF/OVMF_CODE.fd",
"-machine", "confidential-guest-support=sev0,memory-backend=ram1,igvm-cfg=igvm0",
"-object", "memory-backend-memfd,id=ram1,size=2048M,share=true,prealloc=false",
"-object", "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1",
"-object", "igvm-cfg,id=igvm0,file=/test/path/cocos-igvm.igvm",
"-kernel", "img/bzImage",
"-append", "\"quiet console=null\"",
"-initrd", "img/rootfs.cpio.gz",
@@ -167,37 +171,6 @@ func TestConstructQemuArgs(t *testing.T) {
}
}
func TestConstructQemuArgs_KernelHash(t *testing.T) {
config := Config{
EnableSEVSNP: true,
KernelHash: true,
SevConfig: SevConfig{
ID: "sev0",
CBitPos: 51,
ReducedPhysBits: 1,
},
}
result := config.ConstructQemuArgs()
expected := "-object"
expectedValue := "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on"
found := false
for i, arg := range result {
if arg == expected && i+1 < len(result) {
if result[i+1] == expectedValue {
found = true
break
}
}
}
if !found {
t.Errorf("ConstructQemuArgs() did not contain expected SEV-SNP configuration with kernel hashes enabled")
}
}
func TestConstructQemuArgs_HostData(t *testing.T) {
config := Config{
EnableSEVSNP: true,
@@ -205,6 +178,7 @@ func TestConstructQemuArgs_HostData(t *testing.T) {
ID: "sev0",
CBitPos: 51,
ReducedPhysBits: 1,
EnableHostData: true,
HostData: "test-host-data",
},
}
+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()
+13 -10
View File
@@ -4,6 +4,7 @@ package qemu
import (
"fmt"
"log/slog"
"os"
"os/exec"
"syscall"
@@ -30,17 +31,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,
}
}
@@ -59,7 +62,7 @@ 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)
if !v.vmi.Config.KernelHash {
if !v.vmi.Config.EnableSEVSNP {
// Copy firmware vars file.
srcFile := v.vmi.Config.OVMFVarsConfig.File
dstFile := fmt.Sprintf("%s/%s-%s.fd", tmpDir, firmwareVars, id)
@@ -76,8 +79,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()
}
+4 -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)
+55 -20
View File
@@ -3,26 +3,29 @@
package manager
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"fmt"
"log/slog"
"net"
"os"
"os/exec"
"regexp"
"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/manager"
"golang.org/x/crypto/sha3"
"google.golang.org/protobuf/encoding/protojson"
)
const (
@@ -33,6 +36,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"
@@ -76,8 +81,8 @@ 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)
}
type managerService struct {
@@ -85,6 +90,8 @@ type managerService struct {
ap sync.Mutex
qemuCfg qemu.Config
attestationPolicyBinaryPath string
igvmMeasurementBinaryPath string
pcrValuesFilePath string
logger *slog.Logger
vms map[string]vm.VM
vmFactory vm.Provider
@@ -97,7 +104,7 @@ type managerService struct {
var _ Service = (*managerService)(nil)
// New instantiates the manager service implementation.
func New(cfg qemu.Config, attestationPolicyBinPath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
func New(cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
start, end, err := decodeRange(cfg.HostFwdRange)
if err != nil {
return nil, err
@@ -114,6 +121,8 @@ func New(cfg qemu.Config, attestationPolicyBinPath string, logger *slog.Logger,
vms: make(map[string]vm.VM),
vmFactory: vmFactory,
attestationPolicyBinaryPath: attestationPolicyBinPath,
igvmMeasurementBinaryPath: igvmMeasurementBinaryPath,
pcrValuesFilePath: pcrValuesFilePath,
portRangeMin: start,
portRangeMax: end,
persistence: persistence,
@@ -150,30 +159,36 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
cfg.Config.EnvMount = tmpEnvDir
if ms.qemuCfg.EnableSEVSNP || ms.qemuCfg.EnableSEV {
cmd := exec.Command("sudo", fmt.Sprintf("%s/attestation_policy", ms.attestationPolicyBinaryPath), "--policy", "196608")
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 err != nil {
return "", id, err
}
ms.ap.Lock()
_, err := cmd.Output()
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
ms.ap.Unlock()
if err != nil {
return "", id, errors.Wrap(ErrFailedToCreateAttestationPolicy, err)
}
ms.ap.Lock()
f, err := os.ReadFile("./attestation_policy.json")
ms.ap.Unlock()
if err != nil {
return "", id, errors.Wrap(ErrFailedToReadPolicy, err)
}
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
var attestationPolicy check.Config
if err = protojson.Unmarshal(f, &attestationPolicy); err != nil {
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
return "", id, errors.Wrap(ErrUnmarshalFailed, err)
}
// Define the TCB that was present at launch of the VM.
cfg.LaunchTCB = attestationPolicy.Policy.MinimumLaunchTcb
cfg.LaunchTCB = attestationPolicy.Config.Policy.MinimumLaunchTcb
}
agentPort, err := getFreePort(ms.portRangeMin, ms.portRangeMax)
@@ -204,7 +219,7 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
cfg.Config.SevConfig.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
}
@@ -212,6 +227,24 @@ 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
}
go func() {
select {
case <-time.After(ttl):
if err := ms.RemoveVM(ctx, id); err != nil {
ms.logger.Error("Failed to remove VM after TTL", "error", err)
}
case <-ctx.Done():
return
}
}()
}
pid := cvm.GetProcess()
state := qemu.VMState{
@@ -251,7 +284,7 @@ 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
}
@@ -338,7 +371,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)
@@ -402,6 +435,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 {
+4 -2
View File
@@ -9,6 +9,7 @@ import (
"net"
"os"
"os/exec"
"path"
"testing"
mglog "github.com/absmach/magistrala/logger"
@@ -29,7 +30,7 @@ func TestNew(t *testing.T) {
logger := slog.Default()
vmf := new(mocks.Provider)
service, err := New(cfg, "", logger, vmf.Execute, "")
service, err := New(cfg, "", "", "", logger, vmf.Execute, "")
require.NoError(t, err)
assert.NotNil(t, service)
@@ -94,7 +95,8 @@ func TestRun(t *testing.T) {
ms := &managerService{
qemuCfg: qemuCfg,
attestationPolicyBinaryPath: tempDir,
attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"),
pcrValuesFilePath: tempDir,
logger: logger,
vms: make(map[string]vm.VM),
vmFactory: vmf.Execute,
+3 -3
View File
@@ -42,9 +42,9 @@ 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)
}
+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()
+7 -7
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
@@ -25,7 +25,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 +70,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 +117,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()
@@ -254,7 +254,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 +299,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 +344,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()
+3 -1
View File
@@ -3,6 +3,8 @@
package vm
import (
"log/slog"
"github.com/ultravioletrs/cocos/agent"
pkgmanager "github.com/ultravioletrs/cocos/pkg/manager"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -21,7 +23,7 @@ type VM interface {
GetConfig() interface{}
}
type Provider func(config interface{}, computationId string) VM
type Provider func(config interface{}, computationId string, logger *slog.Logger) VM
type Event struct {
EventType string
+22 -1
View File
@@ -100,8 +100,29 @@ packages:
mockname: "{{.InterfaceName}}"
github.com/ultravioletrs/cocos/agent/cvms/server:
interfaces:
AgentServerProvider:
AgentServer:
config:
dir: "{{.InterfaceDir}}/mocks"
filename: "server.go"
mockname: "{{.InterfaceName}}"
github.com/google/go-sev-guest/client:
interfaces:
LeveledQuoteProvider:
config:
dir: "./pkg/attestation/quoteprovider/mocks"
filename: "QuoteProvider.go"
mockname: "{{.InterfaceName}}"
github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage:
interfaces:
Storage:
config:
dir: "{{.InterfaceDir}}/mocks"
filename: "storage.go"
mockname: "{{.InterfaceName}}"
github.com/ultravioletrs/cocos/pkg/clients/grpc:
interfaces:
Client:
config:
dir: "{{.InterfaceDir}}/mocks"
filename: "client.go"
mockname: "{{.InterfaceName}}"
+24 -20
View File
@@ -20,8 +20,8 @@ import (
"unsafe"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
)
const (
@@ -51,8 +51,8 @@ var (
errConnCreate = errors.New("could not create connection")
)
type ValidationVerification func(data1, data2 []byte) error
type FetchAttestation func(data1 []byte) ([]byte, error)
type ValidationVerification func(data1, data2, data3, data4 []byte) error
type FetchAttestation func(data1, data2, data3 []byte) ([]byte, error)
func registerFetchAttestation(callback FetchAttestation) uintptr {
handle := cgo.NewHandle(callback)
@@ -70,7 +70,7 @@ func validationVerificationCallback(teeType C.int) uintptr {
case NoTee:
return uintptr(0)
case AmdSevSnp:
return registerValidationVerification(quoteprovider.VerifyAttestationReportTLS)
return registerValidationVerification(vtpm.VTPMVerify)
default:
return uintptr(0)
}
@@ -82,22 +82,24 @@ func fetchAttestationCallback(teeType C.int) uintptr {
case NoTee:
return uintptr(0)
case AmdSevSnp:
return registerFetchAttestation(quoteprovider.FetchAttestation)
return registerFetchAttestation(vtpm.FetchATLSQuote)
default:
return uintptr(0)
}
}
//export callVerificationValidationCallback
func callVerificationValidationCallback(callbackHandle uintptr, attReport *C.uchar, attReportSize C.int, repData *C.uchar) C.int {
func callVerificationValidationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKeyLen C.int, quote *C.uchar, quoteSize C.int, teeNonce *C.uchar, nonce *C.uchar) C.int {
handle := cgo.Handle(callbackHandle)
defer handle.Delete()
callback := handle.Value().(ValidationVerification)
attestationReport := C.GoBytes(unsafe.Pointer(attReport), attReportSize)
reportData := C.GoBytes(unsafe.Pointer(repData), agent.ReportDataSize)
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
attestationReport := C.GoBytes(unsafe.Pointer(quote), quoteSize)
teeData := C.GoBytes(unsafe.Pointer(teeNonce), quoteprovider.Nonce)
nonceData := C.GoBytes(unsafe.Pointer(nonce), vtpm.Nonce)
err := callback(attestationReport, reportData)
err := callback(attestationReport, pubKeyCert, teeData, nonceData)
if err != nil {
fmt.Fprintf(os.Stderr, "callback failed %v", err)
return C.int(-1)
@@ -107,20 +109,22 @@ func callVerificationValidationCallback(callbackHandle uintptr, attReport *C.uch
}
//export callFetchAttestationCallback
func callFetchAttestationCallback(callbackHandle uintptr, reportDataByte *C.uchar, outlen *C.int) *C.uchar {
func callFetchAttestationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKeyLen C.int, teeNonceByte *C.uchar, vTPMNonceByte *C.uchar, outlen *C.ulong) *C.uchar {
handle := cgo.Handle(callbackHandle)
defer handle.Delete()
callback := handle.Value().(FetchAttestation)
reportData := C.GoBytes(unsafe.Pointer(reportDataByte), agent.ReportDataSize)
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
teeNonceData := C.GoBytes(unsafe.Pointer(teeNonceByte), quoteprovider.Nonce)
vTPMNonce := C.GoBytes(unsafe.Pointer(vTPMNonceByte), vtpm.Nonce)
quote, err := callback(reportData)
quote, err := callback(pubKeyCert, teeNonceData, vTPMNonce)
if err != nil {
fmt.Fprintf(os.Stderr, "attestation callback returned nil")
return nil
}
*outlen = C.int(len(quote))
*outlen = C.ulong(len(quote))
resultC := C.malloc(C.size_t(len(quote)))
if resultC == nil {
fmt.Fprintf(os.Stderr, "could not allocate memory for fetch attestation callback")
@@ -232,23 +236,23 @@ func (c *ATLSConn) Read(b []byte) (int, error) {
case noError:
return n, nil // no error.
case errorZeroReturn:
fmt.Fprintf(os.Stdout, "Connection closed by peer")
fmt.Fprintf(os.Stdout, "Connection closed by peer\n")
return 0, io.EOF // connection closed.
case errorWantRead:
fmt.Fprintf(os.Stderr, "Operation read incomplete, retry later")
fmt.Fprintf(os.Stderr, "Operation read incomplete, retry later\n")
return 0, nil // non-fatal, just retry later.
case errorWantWrite:
fmt.Fprintf(os.Stderr, "Operation write incomplete, retry later")
fmt.Fprintf(os.Stderr, "Operation write incomplete, retry later\n")
return 0, nil // non-fatal, just retry later.
case errorSyscall:
fmt.Fprintf(os.Stderr, "I/O error")
fmt.Fprintf(os.Stderr, "I/O error\n")
return 0, syscall.ECONNRESET // return connection reset error.
case errorSsl:
fmt.Fprintf(os.Stderr, "I/O error")
fmt.Fprintf(os.Stderr, "I/O error\n")
return 0, syscall.ECONNRESET // return connection reset error.
default:
fmt.Fprintf(os.Stderr, "SSL error occurred: %d\n", errCode)
return 0, fmt.Errorf("SSL error")
return 0, fmt.Errorf("SSL error\n")
}
}
@@ -297,7 +301,7 @@ func (c *ATLSConn) Close() error {
return errTLSConn
} else if int(ret) == 1 {
c.tlsConn = nil
break;
break
}
}
+74 -97
View File
@@ -7,30 +7,27 @@
#include <fcntl.h>
#include <unistd.h>
extern int callVerificationValidationCallback(uintptr_t callbackHandle, const u_char* attReport, int attReportSize, const u_char* repData);
extern u_char* callFetchAttestationCallback(uintptr_t callbackHandle, const u_char* reportDataByte, int* outlen);
extern int callVerificationValidationCallback(uintptr_t callbackHandle, const u_char* pubKey, int pubKeyLen, const u_char* quote, int quoteSize, const u_char* teeNonce, const u_char* nonce);
extern u_char* callFetchAttestationCallback(uintptr_t callbackHandle, const u_char* pubKey, int pubKeyLen, const u_char* teeNonceByte, const u_char* vTPMNonceByte, unsigned long* outlen);
extern uintptr_t validationVerificationCallback(int teeType);
extern uintptr_t fetchAttestationCallback(int teeType);
int triggerVerificationValidationCallback(uintptr_t callbackHandle, u_char *attestationReport, int reportSize, u_char *reportData) {
if (attestationReport == NULL || reportData == NULL) {
fprintf(stderr, "attestation data and report data cannot be NULL\n");
int triggerVerificationValidationCallback(uintptr_t callbackHandle, u_char* pub_key, int pub_key_len, u_char *quote, int quote_size, u_char *tee_nonce, u_char *vtpm_nonce) {
if (quote == NULL || vtpm_nonce == NULL || tee_nonce == NULL || pub_key == NULL) {
fprintf(stderr, "attestation and noce and public key cannot be NULL\n");
return -1;
}
return callVerificationValidationCallback(callbackHandle, attestationReport, reportSize, reportData);
return callVerificationValidationCallback(callbackHandle, pub_key, pub_key_len, quote, quote_size, tee_nonce, vtpm_nonce);
}
u_char* triggerFetchAttestationCallback(uintptr_t callbackHandle, char *reportData) {
int outlen = REPORT_DATA_SIZE;
if(reportData == NULL) {
u_char* triggerFetchAttestationCallback(uintptr_t callback_handle, u_char* pub_key, int pub_key_len, char *tee_nonce, char *vtpm_nonce, unsigned long *outlen) {
if(tee_nonce == NULL || vtpm_nonce == NULL) {
fprintf(stderr, "Report data cannot be NULL");
return NULL;
}
return callFetchAttestationCallback(callbackHandle, reportData, &outlen);
return callFetchAttestationCallback(callback_handle, pub_key, pub_key_len, tee_nonce, vtpm_nonce, outlen);
}
int check_sev_snp() {
@@ -47,46 +44,6 @@ int check_sev_snp() {
return 1;
}
int compute_sha256_of_public_key_nonce(X509 *cert, u_char *nonce, u_char *hash) {
EVP_PKEY *pkey = NULL;
u_char *pubkey_buf = NULL;
u_char *concatinated = NULL;
int pubkey_len = 0;
int totla_len = 0;
pkey = X509_get_pubkey(cert);
if (pkey == NULL) {
fprintf(stderr, "Failed to extract public key from certificate\n");
return 0;
}
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
if (pubkey_len <= 0) {
fprintf(stderr, "Failed to convert public key to DER format\n");
EVP_PKEY_free(pkey);
return -1;
}
totla_len = pubkey_len + CLIENT_RANDOM_SIZE;
concatinated = (u_char*)malloc(totla_len);
if (concatinated == NULL) {
perror("failed to allocate memory");
return -1;
}
memcpy(concatinated, nonce, CLIENT_RANDOM_SIZE);
memcpy(concatinated + CLIENT_RANDOM_SIZE, pubkey_buf, pubkey_len);
// Compute the SHA-512 hash of the DER-encoded public key and the random nonce
SHA512(concatinated, totla_len, hash);
// Clean up
EVP_PKEY_free(pkey);
OPENSSL_free(pubkey_buf);
free(concatinated);
return 0; // Success
}
/*
Evidence request extension
- Contains a random nonce that goes into the attestation report
@@ -121,9 +78,14 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
}
if (ext_data != NULL) {
if (RAND_bytes(ext_data->er.data, CLIENT_RANDOM_SIZE) != 1) {
perror("could not generate random bytes, will use SSL client random");
SSL_get_client_random(s, ext_data->er.data, CLIENT_RANDOM_SIZE);
if (RAND_bytes(ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE) != 1) {
perror("could not generate random bytes for vtpm nonce, will use SSL client random");
SSL_get_client_random(s, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
}
if (RAND_bytes(ext_data->er.tee_nonce, REPORT_DATA_SIZE) != 1) {
perror("could not generate random bytes for tee nonce, will use SSL client random");
SSL_get_client_random(s, ext_data->er.tee_nonce, REPORT_DATA_SIZE);
}
} else {
fprintf(stderr, "add_arg is NULL\n");
@@ -132,7 +94,8 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
return -1;
}
memcpy(er->data, ext_data->er.data, CLIENT_RANDOM_SIZE);
memcpy(er->vtpm_nonce, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
memcpy(er->tee_nonce, ext_data->er.tee_nonce, REPORT_DATA_SIZE);
er->tee_type = AMD_TEE;
ext_data->er.tee_type = AMD_TEE;
@@ -201,7 +164,8 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
evidence_request *er = (evidence_request*)in;
if (ext_data != NULL) {
memcpy(ext_data->er.data, er->data, CLIENT_RANDOM_SIZE);
memcpy(ext_data->er.vtpm_nonce, er->vtpm_nonce, CLIENT_RANDOM_SIZE);
memcpy(ext_data->er.tee_nonce, er->tee_nonce, REPORT_DATA_SIZE);
ext_data->er.tee_type = er->tee_type;
} else {
fprintf(stderr, "parse_arg is NULL\n");
@@ -238,7 +202,7 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
/*
Attestation Certificate extension
- Contains the attestation report
- The attestation report contains the hash of the nonce and the Public Key of the x.509 Agent certificate
- The attestation report contains the hash of the nonce, the Public Key of the x.509 Agent certificate, and the vTPM AK
*/
void attestation_certificate_ext_free_cb(SSL *s, unsigned int ext_type,
unsigned int context,
@@ -263,40 +227,46 @@ int attestation_certificate_ext_add_cb(SSL *s, unsigned int ext_type,
{
tls_extension_data *ext_data = (tls_extension_data*)add_arg;
if (ext_data != NULL) {
u_char *attestation_report;
u_char *hash = (u_char*)malloc(REPORT_DATA_SIZE*sizeof(u_char));
if (hash == NULL) {
perror("could not allocate memory");
*al = SSL_AD_INTERNAL_ERROR;
return -1;
}
u_char *quote;
size_t len = 0;
EVP_PKEY *pkey = NULL;
u_char *pubkey_buf = NULL;
int pubkey_len = 0;
if (x != NULL) {
int ret = compute_sha256_of_public_key_nonce(x, ext_data->er.data, hash);
if (ret != 0) {
fprintf(stderr, "error while calculating hash\n");
free(hash);
*al = SSL_AD_INTERNAL_ERROR;
pkey = X509_get_pubkey(x);
if (pkey == NULL) {
fprintf(stderr, "Failed to extract public key from certificate\n");
return -1;
}
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
if (pubkey_len <= 0) {
fprintf(stderr, "Failed to convert public key to DER format\n");
EVP_PKEY_free(pkey);
return -1;
}
} else {
fprintf(stderr, "agent certificate must be used for aTLS\n");
free(hash);
*al = SSL_AD_INTERNAL_ERROR;
return -1;
}
attestation_report = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, hash);
if (attestation_report == NULL) {
quote = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, pubkey_buf, pubkey_len, ext_data->er.tee_nonce, ext_data->er.vtpm_nonce, &len);
if (quote == NULL) {
fprintf(stderr, "attestation report is NULL\n");
*al = SSL_AD_INTERNAL_ERROR;
EVP_PKEY_free(pkey);
OPENSSL_free(pubkey_buf);
return -1;
}
free(hash);
*out = attestation_report;
*outlen = ATTESTATION_REPORT_SIZE;
EVP_PKEY_free(pkey);
OPENSSL_free(pubkey_buf);
*out = quote;
*outlen = len;
return 1;
} else {
fprintf(stderr, "add_arg is NULL\n");
@@ -329,34 +299,41 @@ int attestation_certificate_ext_parse_cb(SSL *s, unsigned int ext_type,
tls_extension_data *ext_data = (tls_extension_data*)parse_arg;
if (ext_data != NULL) {
char *attestation_report = (char*)malloc(ATTESTATION_REPORT_SIZE*sizeof(char));
u_char *hash = (u_char*)malloc(REPORT_DATA_SIZE*sizeof(u_char));
char *quote = (char*)malloc(inlen*sizeof(char));
EVP_PKEY *pkey = NULL;
u_char *pubkey_buf = NULL;
int pubkey_len = 0;
int res = 0;
if (hash == NULL || attestation_report == NULL) {
if (quote == NULL) {
perror("could not allocate memory");
if (hash != NULL) free(hash);
if (attestation_report != NULL) free(attestation_report);
return 0;
}
if (compute_sha256_of_public_key_nonce(x, ext_data->er.data, hash) != 0) {
fprintf(stderr, "calculating hash failed\n");
free(attestation_report);
free(hash);
return 0;
pkey = X509_get_pubkey(x);
if (pkey == NULL) {
fprintf(stderr, "Failed to extract public key from certificate\n");
return -1;
}
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
if (pubkey_len <= 0) {
fprintf(stderr, "Failed to convert public key to DER format\n");
EVP_PKEY_free(pkey);
return -1;
}
memcpy(quote, in, inlen);
memcpy(attestation_report, in, inlen);
res = triggerVerificationValidationCallback(ext_data->verification_validation_handler,
attestation_report,
ATTESTATION_REPORT_SIZE,
hash);
free(attestation_report);
free(hash);
res = triggerVerificationValidationCallback(ext_data->verification_validation_handler,
pubkey_buf,
pubkey_len,
quote,
inlen,
(u_char*)&ext_data->er.tee_nonce,
(u_char*)&ext_data->er.vtpm_nonce);
free(quote);
EVP_PKEY_free(pkey);
OPENSSL_free(pubkey_buf);
if (res != 0) {
fprintf(stderr, "verification and validation failed, aborting connection\n");
+3 -3
View File
@@ -6,7 +6,6 @@
#define EVIDENCE_REQUEST_HELLO_EXTENSION_TYPE 65
#define ATTESTATION_CERTIFICATE_EXTENSION_TYPE 66
#define ATTESTATION_REPORT_SIZE 0x4A0
#define REPORT_DATA_SIZE 64
#define CLIENT_RANDOM_SIZE 32
#define TLS_CLIENT_CTX 0
@@ -19,7 +18,8 @@
typedef struct evidence_request
{
int tee_type;
char data[CLIENT_RANDOM_SIZE];
char vtpm_nonce[CLIENT_RANDOM_SIZE];
char tee_nonce[REPORT_DATA_SIZE];
} evidence_request;
typedef struct tls_extension_data
@@ -32,7 +32,7 @@ typedef struct tls_extension_data
typedef struct tls_server_connection
{
int server_fd;
char* cert;
char* cert;
int cert_len;
char* key;
int key_len;
+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cmdconfig
import (
"bytes"
"fmt"
"io"
"os/exec"
)
var IgvmMeasureOptions = []string{"measure", "-b"}
type MeasurementProvider interface {
Run(binaryPath string) ([]byte, error)
Stop() error
}
type CmdConfig struct {
binPath string
options []string
stderr io.Writer
cmd *exec.Cmd
execCommand func(name string, arg ...string) *exec.Cmd
}
func NewCmdConfig(binPath string, options []string, stderr io.Writer) (*CmdConfig, error) {
if binPath == "" {
return nil, fmt.Errorf("pathToBinary cannot be empty")
}
return &CmdConfig{
binPath: binPath,
options: options,
stderr: stderr,
execCommand: exec.Command,
}, nil
}
func (m *CmdConfig) Run(pathToFile string) ([]byte, error) {
binary := m.binPath
args := []string{}
args = append(args, pathToFile)
args = append(args, m.options...)
outBuf := &bytes.Buffer{}
cmd := m.execCommand(binary, args...)
cmd.Stderr = m.stderr
cmd.Stdout = outBuf
if err := cmd.Run(); err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
func (m *CmdConfig) Stop() error {
if m.cmd == nil || m.cmd.Process == nil {
return fmt.Errorf("no running process to stop")
}
if err := m.cmd.Process.Kill(); err != nil {
return fmt.Errorf("failed to stop process: %v", err)
}
return nil
}
// SetExecCommand allows tests to inject a mock execCommand function.
func (m *CmdConfig) SetExecCommand(cmdFunc func(name string, arg ...string) *exec.Cmd) {
m.execCommand = cmdFunc
}
@@ -0,0 +1,76 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cmdconfig
import (
"bytes"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIgvmMeasurement(t *testing.T) {
tests := []struct {
name string
setup func() *CmdConfig
runArgs string
expectErr bool
expectedErr string
}{
{
name: "NewIgvmMeasurement - Empty pathToBinary",
setup: func() *CmdConfig {
igvm, err := NewCmdConfig("", []string{""}, nil)
assert.Error(t, err)
assert.Nil(t, igvm)
return nil
},
expectErr: true,
expectedErr: "pathToBinary cannot be empty",
},
{
name: "Run - Successful Execution",
setup: func() *CmdConfig {
igvm, _ := NewCmdConfig("/valid/path", []string{""}, nil)
igvm.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command("sh", "-c", "echo 'measurement successful'")
})
return igvm
},
expectErr: false,
},
{
name: "Run - Failure Execution",
setup: func() *CmdConfig {
igvm, _ := NewCmdConfig("/invalid/path", []string{""}, nil)
igvm.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
return exec.Command("sh", "-c", "echo 'some error occurred\nextra line' && exit 1")
})
return igvm
},
expectErr: true,
expectedErr: "exit status 1",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
igvm := tc.setup()
if igvm != nil {
buf := new(bytes.Buffer)
igvm.stderr = buf
_, err := igvm.Run(tc.runArgs)
if tc.expectErr {
assert.Error(t, err)
assert.Equal(t, strings.TrimSpace(tc.expectedErr), strings.TrimSpace(err.Error()))
} else {
assert.NoError(t, err)
}
}
})
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package config
import (
"encoding/json"
"os"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/proto/check"
"google.golang.org/protobuf/encoding/protojson"
)
type AttestationType int32
const (
SNP AttestationType = iota
VTPM
SNPvTPM
)
var (
AttestationPolicy = Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &PcrConfig{}}
ErrAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
ErrAttestationPolicyEncode = errors.New("failed to encode the Attestation Policy")
)
type PcrValues struct {
Sha256 map[string]string `json:"sha256"`
Sha384 map[string]string `json:"sha384"`
Sha1 map[string]string `json:"sha1"`
}
type PcrConfig struct {
PCRValues PcrValues `json:"pcr_values"`
}
type Config struct {
*check.Config
*PcrConfig
}
func ReadAttestationPolicy(policyPath string, attestationConfiguration *Config) error {
if policyPath != "" {
policyData, err := os.ReadFile(policyPath)
if err != nil {
return errors.Wrap(ErrAttestationPolicyOpen, err)
}
return ReadAttestationPolicyFromByte(policyData, attestationConfiguration)
}
return ErrAttestationPolicyMissing
}
func ReadAttestationPolicyFromByte(policyData []byte, attestationConfiguration *Config) error {
unmarshalOptions := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.Config); err != nil {
return errors.Wrap(ErrAttestationPolicyDecode, err)
}
if err := json.Unmarshal(policyData, attestationConfiguration.PcrConfig); err != nil {
return errors.Wrap(ErrAttestationPolicyDecode, err)
}
return nil
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package gcp
import (
"context"
"fmt"
"io"
"cloud.google.com/go/storage"
"github.com/golang/protobuf/proto"
"github.com/google/gce-tcb-verifier/proto/endorsement"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/tools/lib/report"
config "github.com/ultravioletrs/cocos/pkg/attestation"
)
const (
// Offset of the 384-bit measurement in the report.
// The measurement is 48 bytes long and starts at offset 0x90.
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf page 56
measurementOffset = 0x90
measurementSize = 48
bucketName = "gce_tcb_integrity"
objectName = "ovmf_x64_csm/sevsnp/%s.binarypb"
ovmfObjectName = "ovmf_x64_csm/%s.fd"
)
func Extract384BitMeasurement(attestation *sevsnp.Attestation) (string, error) {
if attestation == nil {
return "", fmt.Errorf("report is nil")
}
reportBin, err := report.Transform(attestation, "bin")
if err != nil {
return "", fmt.Errorf("failed to transform report to binary: %v", err)
}
if len(reportBin) < measurementOffset+measurementSize {
return "", fmt.Errorf("report is too short to contain the 384-bit measurement")
}
measurement := reportBin[measurementOffset : measurementOffset+measurementSize]
return fmt.Sprintf("%x", measurement), nil
}
func GetLaunchEndorsement(ctx context.Context, measurement384 string) (*endorsement.VMGoldenMeasurement, error) {
client, err := storage.NewClient(ctx)
if err != nil {
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to create storage client: %v", err)
}
reader, err := client.Bucket(bucketName).Object(fmt.Sprintf(objectName, measurement384)).NewReader(ctx)
if err != nil {
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to create reader: %v", err)
}
defer reader.Close()
launchEndorsements, err := io.ReadAll(reader)
if err != nil {
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to read object: %v", err)
}
var endorsementPB endorsement.VMLaunchEndorsement
if err := proto.Unmarshal(launchEndorsements, &endorsementPB); err != nil {
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to unmarshal launch endorsement: %v", err)
}
var goldenUEFI endorsement.VMGoldenMeasurement
if err := proto.Unmarshal(endorsementPB.SerializedUefiGolden, &goldenUEFI); err != nil {
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to unmarshal golden UEFI: %v", err)
}
return &goldenUEFI, nil
}
func GenerateAttestationPolicy(endorsement *endorsement.VMGoldenMeasurement, vcpuNum uint32) (*config.Config, error) {
attestationPolicy := config.Config{PcrConfig: &config.PcrConfig{}, Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}}
attestationPolicy.Config.Policy.Policy = endorsement.SevSnp.Policy
attestationPolicy.Config.Policy.Measurement = endorsement.SevSnp.Measurements[vcpuNum]
attestationPolicy.Config.RootOfTrust.DisallowNetwork = false
attestationPolicy.Config.RootOfTrust.CheckCrl = true
attestationPolicy.Config.RootOfTrust.Product = "Milan"
attestationPolicy.Config.RootOfTrust.ProductLine = "Milan"
return &attestationPolicy, nil
}
func DownloadOvmfFile(ctx context.Context, digest string) ([]byte, error) {
client, err := storage.NewClient(ctx)
if err != nil {
return []byte{}, fmt.Errorf("failed to create storage client: %v", err)
}
reader, err := client.Bucket(bucketName).Object(fmt.Sprintf(ovmfObjectName, digest)).NewReader(ctx)
if err != nil {
return []byte{}, fmt.Errorf("failed to create reader: %v", err)
}
defer reader.Close()
ovmf, err := io.ReadAll(reader)
if err != nil {
return []byte{}, fmt.Errorf("failed to read object: %v", err)
}
return ovmf, nil
}
+7 -9
View File
@@ -8,26 +8,24 @@ package quoteprovider
import (
"github.com/google/go-sev-guest/client"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
pb "github.com/google/go-sev-guest/proto/sevsnp"
cocosai "github.com/ultravioletrs/cocos"
)
var (
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
)
const Nonce = 64
var _ client.QuoteProvider = (*embeddedQuoteProvider)(nil)
var _ client.LeveledQuoteProvider = (*embeddedQuoteProvider)(nil)
type embeddedQuoteProvider struct {
}
func GetQuoteProvider() (client.QuoteProvider, error) {
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
return &embeddedQuoteProvider{}, nil
}
// GetQuote returns the SEV quote for the given report data.
func (e *embeddedQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) {
// GetRawQuoteAtLevel returns the SEV quote for the given report data and VMPL.
func (e *embeddedQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]byte, error) {
return cocosai.EmbeddedAttestation, nil
}
@@ -46,6 +44,6 @@ func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
return cocosai.EmbeddedAttestation, nil
}
func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error {
func VerifyAttestationReportTLS(attestation *sevsnp.Attestation, reportData []byte) error {
return nil
}
@@ -1,4 +1,7 @@
// Code generated by mockery v2.45.0. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Code generated by mockery v2.53.3. DO NOT EDIT.
package mocks
@@ -7,34 +10,42 @@ import (
mock "github.com/stretchr/testify/mock"
)
// QuoteProvider is an autogenerated mock type for the QuoteProvider type
type QuoteProvider struct {
// LeveledQuoteProvider is an autogenerated mock type for the LeveledQuoteProvider type
type LeveledQuoteProvider struct {
mock.Mock
}
// GetRawQuote provides a mock function with given fields: reportData
func (_m *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
ret := _m.Called(reportData)
type LeveledQuoteProvider_Expecter struct {
mock *mock.Mock
}
func (_m *LeveledQuoteProvider) EXPECT() *LeveledQuoteProvider_Expecter {
return &LeveledQuoteProvider_Expecter{mock: &_m.Mock}
}
// GetRawQuoteAtLevel provides a mock function with given fields: reportData, vmpl
func (_m *LeveledQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error) {
ret := _m.Called(reportData, vmpl)
if len(ret) == 0 {
panic("no return value specified for GetRawQuote")
panic("no return value specified for GetRawQuoteAtLevel")
}
var r0 []uint8
var r1 error
if rf, ok := ret.Get(0).(func([64]byte) ([]uint8, error)); ok {
return rf(reportData)
if rf, ok := ret.Get(0).(func([64]byte, uint) ([]uint8, error)); ok {
return rf(reportData, vmpl)
}
if rf, ok := ret.Get(0).(func([64]byte) []uint8); ok {
r0 = rf(reportData)
if rf, ok := ret.Get(0).(func([64]byte, uint) []uint8); ok {
r0 = rf(reportData, vmpl)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]uint8)
}
}
if rf, ok := ret.Get(1).(func([64]byte) error); ok {
r1 = rf(reportData)
if rf, ok := ret.Get(1).(func([64]byte, uint) error); ok {
r1 = rf(reportData, vmpl)
} else {
r1 = ret.Error(1)
}
@@ -42,8 +53,37 @@ func (_m *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
return r0, r1
}
// IsSupported provides a mock function with given fields:
func (_m *QuoteProvider) IsSupported() bool {
// LeveledQuoteProvider_GetRawQuoteAtLevel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRawQuoteAtLevel'
type LeveledQuoteProvider_GetRawQuoteAtLevel_Call struct {
*mock.Call
}
// GetRawQuoteAtLevel is a helper method to define mock.On call
// - reportData [64]byte
// - vmpl uint
func (_e *LeveledQuoteProvider_Expecter) GetRawQuoteAtLevel(reportData interface{}, vmpl interface{}) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
return &LeveledQuoteProvider_GetRawQuoteAtLevel_Call{Call: _e.mock.On("GetRawQuoteAtLevel", reportData, vmpl)}
}
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Run(run func(reportData [64]byte, vmpl uint)) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([64]byte), args[1].(uint))
})
return _c
}
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Return(_a0 []uint8, _a1 error) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) RunAndReturn(run func([64]byte, uint) ([]uint8, error)) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
_c.Call.Return(run)
return _c
}
// IsSupported provides a mock function with no fields
func (_m *LeveledQuoteProvider) IsSupported() bool {
ret := _m.Called()
if len(ret) == 0 {
@@ -60,8 +100,35 @@ func (_m *QuoteProvider) IsSupported() bool {
return r0
}
// Product provides a mock function with given fields:
func (_m *QuoteProvider) Product() *sevsnp.SevProduct {
// LeveledQuoteProvider_IsSupported_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSupported'
type LeveledQuoteProvider_IsSupported_Call struct {
*mock.Call
}
// IsSupported is a helper method to define mock.On call
func (_e *LeveledQuoteProvider_Expecter) IsSupported() *LeveledQuoteProvider_IsSupported_Call {
return &LeveledQuoteProvider_IsSupported_Call{Call: _e.mock.On("IsSupported")}
}
func (_c *LeveledQuoteProvider_IsSupported_Call) Run(run func()) *LeveledQuoteProvider_IsSupported_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *LeveledQuoteProvider_IsSupported_Call) Return(_a0 bool) *LeveledQuoteProvider_IsSupported_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *LeveledQuoteProvider_IsSupported_Call) RunAndReturn(run func() bool) *LeveledQuoteProvider_IsSupported_Call {
_c.Call.Return(run)
return _c
}
// Product provides a mock function with no fields
func (_m *LeveledQuoteProvider) Product() *sevsnp.SevProduct {
ret := _m.Called()
if len(ret) == 0 {
@@ -80,13 +147,40 @@ func (_m *QuoteProvider) Product() *sevsnp.SevProduct {
return r0
}
// NewQuoteProvider creates a new instance of QuoteProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// LeveledQuoteProvider_Product_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Product'
type LeveledQuoteProvider_Product_Call struct {
*mock.Call
}
// Product is a helper method to define mock.On call
func (_e *LeveledQuoteProvider_Expecter) Product() *LeveledQuoteProvider_Product_Call {
return &LeveledQuoteProvider_Product_Call{Call: _e.mock.On("Product")}
}
func (_c *LeveledQuoteProvider_Product_Call) Run(run func()) *LeveledQuoteProvider_Product_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *LeveledQuoteProvider_Product_Call) Return(_a0 *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *LeveledQuoteProvider_Product_Call) RunAndReturn(run func() *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
_c.Call.Return(run)
return _c
}
// NewLeveledQuoteProvider creates a new instance of LeveledQuoteProvider. 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 NewQuoteProvider(t interface {
func NewLeveledQuoteProvider(t interface {
mock.TestingT
Cleanup(func())
}) *QuoteProvider {
mock := &QuoteProvider{}
}) *LeveledQuoteProvider {
mock := &LeveledQuoteProvider{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
+20 -28
View File
@@ -14,7 +14,6 @@ import (
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/client"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
@@ -22,6 +21,7 @@ import (
"github.com/google/go-sev-guest/verify"
"github.com/google/go-sev-guest/verify/trust"
"github.com/google/logger"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"google.golang.org/protobuf/proto"
)
@@ -29,20 +29,19 @@ const (
cocosDirectory = ".cocos"
caBundleName = "ask_ark.pem"
attestationReportSize = 0x4A0
reportDataSize = 64
Nonce = 64
sevProductNameMilan = "Milan"
sevProductNameGenoa = "Genoa"
sevVMPL = 2
)
var (
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
timeout = time.Minute * 2
maxTryDelay = time.Second * 30
timeout = time.Minute * 2
maxTryDelay = time.Second * 30
)
var (
errProductLine = errors.New(fmt.Sprintf("product name must be %s or %s", sevProductNameMilan, sevProductNameGenoa))
errReportSize = errors.New("attestation report size mismatch")
errAttVerification = errors.New("attestation verification failed")
errAttValidation = errors.New("attestation validation failed")
)
@@ -138,38 +137,31 @@ func validateReport(attestationPB *sevsnp.Attestation, cfg *check.Config) error
return nil
}
func GetQuoteProvider() (client.QuoteProvider, error) {
return client.GetQuoteProvider()
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
return client.GetLeveledQuoteProvider()
}
func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error {
config, err := copyConfig(&AttConfigurationSEVSNP)
func VerifyAttestationReportTLS(attestationPB *sevsnp.Attestation, reportData []byte) error {
config, err := copyConfig(config.AttestationPolicy.Config)
if err != nil {
return errors.Wrap(fmt.Errorf("failed to create a copy of attestation policy"), err)
}
// Certificate chain is populated based on the extra data that is appended to the SEV-SNP attestation report.
// This data is not part of the attestation report and it will be ignored.
attestationPB.CertificateChain = nil
config.Policy.ReportData = reportData[:]
return VerifyAndValidate(attestationBytes, config)
return VerifyAndValidate(attestationPB, config)
}
func VerifyAndValidate(attestationReport []byte, cfg *check.Config) error {
func VerifyAndValidate(attestationPB *sevsnp.Attestation, cfg *check.Config) error {
logger.Init("", false, false, io.Discard)
if len(attestationReport) < attestationReportSize {
return errReportSize
}
attestationBytes := attestationReport[:attestationReportSize]
attestationPB, err := abi.ReportCertsToProto(attestationBytes)
if err != nil {
return fmt.Errorf("failed to convert attestation bytes to struct %v", errors.Wrap(errAttVerification, err))
}
if err = verifyReport(attestationPB, cfg); err != nil {
if err := verifyReport(attestationPB, cfg); err != nil {
return err
}
if err = validateReport(attestationPB, cfg); err != nil {
if err := validateReport(attestationPB, cfg); err != nil {
return err
}
@@ -177,19 +169,19 @@ func VerifyAndValidate(attestationReport []byte, cfg *check.Config) error {
}
func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
var reportData [reportDataSize]byte
var reportData [Nonce]byte
qp, err := GetQuoteProvider()
qp, err := GetLeveledQuoteProvider()
if err != nil {
return []byte{}, fmt.Errorf("could not get quote provider")
}
if len(reportData) > reportDataSize {
if len(reportData) > Nonce {
return []byte{}, fmt.Errorf("attestation report size mismatch")
}
copy(reportData[:], reportDataSlice)
rawQuote, err := qp.GetRawQuote(reportData)
rawQuote, err := qp.GetRawQuoteAtLevel(reportData, sevVMPL)
if err != nil {
return []byte{}, fmt.Errorf("failed to get raw quote")
}
+33 -35
View File
@@ -17,14 +17,10 @@ import (
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"google.golang.org/protobuf/encoding/protojson"
)
const (
measurementOffset = 0x90
signatureOffset = 0x2A0
)
func TestFillInAttestationLocal(t *testing.T) {
tempDir, err := os.MkdirTemp("", "test_home")
require.NoError(t, err)
@@ -76,18 +72,18 @@ func TestFillInAttestationLocal(t *testing.T) {
}
func TestVerifyAttestationReportSuccess(t *testing.T) {
file, reportData := prepareForTestVerifyAttestationReport(t)
attestationPB, reportData := prepVerifyAttReport(t)
tests := []struct {
name string
attestationReport []byte
attestationReport *sevsnp.Attestation
reportData []byte
goodProduct int
err error
}{
{
name: "Valid attestation, validation and verification is performed succsessfully",
attestationReport: file,
attestationReport: attestationPB,
reportData: reportData,
goodProduct: 1,
err: nil,
@@ -103,20 +99,20 @@ func TestVerifyAttestationReportSuccess(t *testing.T) {
}
func TestVerifyAttestationReportMalformedSignature(t *testing.T) {
file, reportData := prepareForTestVerifyAttestationReport(t)
attestationPB, reportData := prepVerifyAttReport(t)
// Change random data so in the signature so the signature failes
file[signatureOffset] = file[signatureOffset] ^ 0x01
attestationPB.Report.Signature[0] = attestationPB.Report.Signature[0] ^ 0x01
tests := []struct {
name string
attestationReport []byte
attestationReport *sevsnp.Attestation
reportData []byte
err error
}{
{
name: "Valid attestation, distorted signature",
attestationReport: file,
attestationReport: attestationPB,
reportData: reportData,
err: errAttVerification,
},
@@ -131,17 +127,17 @@ func TestVerifyAttestationReportMalformedSignature(t *testing.T) {
}
func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
file, reportData := prepareForTestVerifyAttestationReport(t)
attestationPB, reportData := prepVerifyAttReport(t)
tests := []struct {
name string
attestationReport []byte
attestationReport *sevsnp.Attestation
reportData []byte
err error
}{
{
name: "Valid attestation, unknown product",
attestationReport: file,
attestationReport: attestationPB,
reportData: reportData,
err: errProductLine,
},
@@ -149,8 +145,8 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
AttConfigurationSEVSNP.RootOfTrust.ProductLine = ""
AttConfigurationSEVSNP.Policy.Product = nil
config.AttestationPolicy.Config.RootOfTrust.ProductLine = ""
config.AttestationPolicy.Config.Policy.Product = nil
err := VerifyAttestationReportTLS(tt.attestationReport, tt.reportData)
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
})
@@ -158,20 +154,20 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
}
func TestVerifyAttestationReportMalformedPolicy(t *testing.T) {
file, reportData := prepareForTestVerifyAttestationReport(t)
attestationPB, reportData := prepVerifyAttReport(t)
// Change random data in the measurement so the measurement does not match
file[measurementOffset] = file[measurementOffset] ^ 0x01
attestationPB.Report.Measurement[0] = attestationPB.Report.Measurement[0] ^ 0x01
tests := []struct {
name string
attestationReport []byte
attestationReport *sevsnp.Attestation
reportData []byte
err error
}{
{
name: "Valid attestation, malformed policy (measurement)",
attestationReport: file,
attestationReport: attestationPB,
reportData: reportData,
err: errAttVerification,
},
@@ -185,32 +181,34 @@ func TestVerifyAttestationReportMalformedPolicy(t *testing.T) {
}
}
func prepareForTestVerifyAttestationReport(t *testing.T) ([]byte, []byte) {
func prepVerifyAttReport(t *testing.T) (*sevsnp.Attestation, []byte) {
file, err := os.ReadFile("../../../attestation.bin")
require.NoError(t, err)
rr, err := abi.ReportCertsToProto(file)
require.NoError(t, err)
if len(file) < attestationReportSize {
file = append(file, make([]byte, attestationReportSize-len(file))...)
}
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
rr, err := abi.ReportCertsToProto(file)
require.NoError(t, err)
config.AttestationPolicy = config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
attestationPolicyFile, err := os.ReadFile("../../../scripts/attestation_policy/attestation_policy.json")
require.NoError(t, err)
err = protojson.Unmarshal(attestationPolicyFile, &AttConfigurationSEVSNP)
unmarshalOptions := protojson.UnmarshalOptions{DiscardUnknown: true}
err = unmarshalOptions.Unmarshal(attestationPolicyFile, config.AttestationPolicy.Config)
require.NoError(t, err)
AttConfigurationSEVSNP.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
AttConfigurationSEVSNP.Policy.FamilyId = rr.Report.FamilyId
AttConfigurationSEVSNP.Policy.ImageId = rr.Report.ImageId
AttConfigurationSEVSNP.Policy.Measurement = rr.Report.Measurement
AttConfigurationSEVSNP.Policy.HostData = rr.Report.HostData
AttConfigurationSEVSNP.Policy.ReportIdMa = rr.Report.ReportIdMa
AttConfigurationSEVSNP.RootOfTrust.ProductLine = sevProductNameMilan
config.AttestationPolicy.Config.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
config.AttestationPolicy.Config.Policy.FamilyId = rr.Report.FamilyId
config.AttestationPolicy.Config.Policy.ImageId = rr.Report.ImageId
config.AttestationPolicy.Config.Policy.Measurement = rr.Report.Measurement
config.AttestationPolicy.Config.Policy.HostData = rr.Report.HostData
config.AttestationPolicy.Config.Policy.ReportIdMa = rr.Report.ReportIdMa
config.AttestationPolicy.Config.RootOfTrust.ProductLine = sevProductNameMilan
return file, rr.Report.ReportData
return rr, rr.Report.ReportData
}
+317
View File
@@ -0,0 +1,317 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package vtpm
import (
"bytes"
"crypto"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/hex"
"fmt"
"io"
"os"
"strconv"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm-tools/proto/attest"
"github.com/google/go-tpm-tools/proto/tpm"
"github.com/google/go-tpm-tools/server"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"golang.org/x/crypto/sha3"
"google.golang.org/protobuf/proto"
)
const (
eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements"
Nonce = 32
PCR15 = 15
Hash256 = 32
Hash384 = 48
)
var (
ExternalTPM io.ReadWriteCloser
ErrNoHashAlgo = errors.New("hash algo is not supported")
)
type VtpmAttest func(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error)
type tpmWrapper struct {
io.ReadWriteCloser
}
func (et tpmWrapper) EventLog() ([]byte, error) {
return os.ReadFile(eventLog)
}
func OpenTpm() (io.ReadWriteCloser, error) {
if ExternalTPM != nil {
return tpmWrapper{ExternalTPM}, nil
}
tw := tpmWrapper{}
var err error
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpmrm0")
if os.IsNotExist(err) {
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpm0")
}
return tw, err
}
func ExtendPCR(pcrIndex int, value []byte) error {
rwc, err := OpenTpm()
if err != nil {
return err
}
defer rwc.Close()
fixedSha256Hash := sha3.Sum256(value)
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA256, fixedSha256Hash[:], ""); err != nil {
return err
}
fixedSha384Hash := sha3.Sum384(value)
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA384, fixedSha384Hash[:], ""); err != nil {
return err
}
return nil
}
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
attestation, err := fetchVTPMQuote(vTPMNonce)
if err != nil {
return []byte{}, err
}
if teeAttestaion {
attestation, err = addTEEAttestation(attestation, teeNonce)
if err != nil {
return []byte{}, err
}
}
return marshalQuote(attestation)
}
func FetchATLSQuote(pubKey, teeNonce, vTPMNonce []byte) ([]byte, error) {
attestation, err := fetchVTPMQuote(vTPMNonce)
if err != nil {
return []byte{}, err
}
reportData, err := createTEEAttestationReportNonce(pubKey, attestation.GetAkPub(), teeNonce)
if err != nil {
return []byte{}, err
}
attestation, err = addTEEAttestation(attestation, reportData)
if err != nil {
return []byte{}, err
}
return marshalQuote(attestation)
}
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte) error {
attestation := &attest.Attestation{}
err := proto.Unmarshal(quote, attestation)
if err != nil {
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
}
ak := attestation.GetAkPub()
pub, err := tpm2.DecodePublic(ak)
if err != nil {
return err
}
cryptoPub, err := pub.Key()
if err != nil {
return err
}
reportData, err := createTEEAttestationReportNonce(pubKeyTLS, ak, teeNonce)
if err != nil {
return errors.Wrap(fmt.Errorf("failed to create TEE attestation report nonce"), err)
}
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), reportData); err != nil {
return fmt.Errorf("failed to verify TEE attestation report: %v", err)
}
_, err = server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: vtpmNonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
if err != nil {
return errors.Wrap(fmt.Errorf("failed to verify attestation"), err)
}
s256, s384 := calculatePCRTLSKey(pubKeyTLS)
if err := checkExpectedPCRValues(attestation, s256, s384); err != nil {
return fmt.Errorf("PCR values do not match expected PCR values: %w", err)
}
return nil
}
// EmptyAttest is a dummy attestation function that returns an empty attestation report.
func EmptyAttest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
return []byte{}, nil
}
func publicKeyToBytes(pubKey interface{}) ([]byte, error) {
derBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, err
}
return derBytes, nil
}
func createTEEAttestationReportNonce(pubKeyTLS []byte, ak []byte, nonce []byte) ([]byte, error) {
pub, err := tpm2.DecodePublic(ak)
if err != nil {
return []byte{}, err
}
cryptoPub, err := pub.Key()
if err != nil {
return []byte{}, err
}
pubKeyBytes, err := publicKeyToBytes(cryptoPub)
if err != nil {
return []byte{}, err
}
reportData := append(append(pubKeyTLS, pubKeyBytes...), nonce...)
hash := sha3.Sum512(reportData)
return hash[:], nil
}
func marshalQuote(attestation *attest.Attestation) ([]byte, error) {
out, err := proto.Marshal(attestation)
if err != nil {
return []byte{}, errors.Wrap(fmt.Errorf("failed to marshal vTPM attestation report"), err)
}
return out, nil
}
func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
rwc, err := OpenTpm()
if err != nil {
return nil, err
}
defer rwc.Close()
attestationKey, err := client.AttestationKeyRSA(rwc)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("failed to create attestation key: %v", err), err)
}
defer attestationKey.Close()
var fixedNonce [Nonce]byte
copy(fixedNonce[:], nonce)
attestOpts := client.AttestOpts{}
attestOpts.Nonce = fixedNonce[:]
attestOpts.TCGEventLog, err = client.GetEventLog(rwc)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("failed to retrieve TCG Event Log: %v", err), err)
}
attestation, err := attestationKey.Attest(attestOpts)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("failed to attest: %v", err), err)
}
return attestation, nil
}
func addTEEAttestation(attestation *attest.Attestation, nonce []byte) (*attest.Attestation, error) {
rawTeeAttestation, err := quoteprovider.FetchAttestation(nonce)
if err != nil {
return attestation, fmt.Errorf("failed to fetch TEE attestation report: %v", err)
}
extReport, err := abi.ReportCertsToProto(rawTeeAttestation)
if err != nil {
return attestation, errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
}
attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
SevSnpAttestation: extReport,
}
return attestation, nil
}
func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256, ePcr384 []byte) error {
quotes := attestation.GetQuotes()
for i := range quotes {
quote := quotes[i]
var pcrMap map[string]string
var pcr15 []byte
switch quote.Pcrs.Hash {
case tpm.HashAlgo_SHA256:
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha256
pcr15 = ePcr256
case tpm.HashAlgo_SHA384:
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha384
pcr15 = ePcr384
case tpm.HashAlgo_SHA1:
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha1
pcr15 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
default:
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
}
pcr15Index := uint32(15)
if !bytes.Equal(quote.Pcrs.Pcrs[pcr15Index], pcr15) {
return fmt.Errorf("for algo %s PCR[15] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], hex.EncodeToString(pcr15), hex.EncodeToString(quote.Pcrs.Pcrs[pcr15Index]))
}
for i, v := range pcrMap {
index, err := strconv.ParseInt(i, 10, 32)
if err != nil {
return errors.Wrap(fmt.Errorf("error converting PCR index to int32"), err)
}
value, err := hex.DecodeString(v)
if err != nil {
return errors.Wrap(fmt.Errorf("error converting PCR value to byte"), err)
}
if !bytes.Equal(quote.Pcrs.Pcrs[uint32(index)], value) {
return fmt.Errorf("for algo %s PCR[%d] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], index, hex.EncodeToString(value), hex.EncodeToString(quote.Pcrs.Pcrs[uint32(index)]))
}
}
}
return nil
}
// Return SHA256 and SHA384 values of the input public key.
func calculatePCRTLSKey(pubKey []byte) ([]byte, []byte) {
init256 := make([]byte, Hash256)
init384 := make([]byte, Hash384)
key256 := sha3.Sum256(pubKey)
key384 := sha3.Sum384(pubKey)
pcrValue256 := append(init256, key256[:]...)
pcrValue384 := append(init384, key384[:]...)
newPcr256 := sha256.Sum256(pcrValue256)
newPcr384 := sha512.Sum384(pcrValue384)
return newPcr256[:], newPcr384[:]
}
+3 -2
View File
@@ -15,6 +15,7 @@ import (
"github.com/ultravioletrs/cocos/agent"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/mocks"
config "github.com/ultravioletrs/cocos/pkg/attestation"
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
@@ -101,7 +102,7 @@ func TestAgentClientIntegration(t *testing.T) {
Timeout: 1,
},
},
err: ErrAgentServiceUnavailable,
err: errors.New("failed to connect to grpc server"),
},
{
name: "invalid config, missing AttestationPolicy with aTLS",
@@ -112,7 +113,7 @@ func TestAgentClientIntegration(t *testing.T) {
},
AttestedTLS: true,
},
err: pkggrpc.ErrAttestationPolicyMissing,
err: config.ErrAttestationPolicyMissing,
},
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
//go:build cgo
package grpc
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"net"
"os"
"strconv"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/pkg/atls"
config "github.com/ultravioletrs/cocos/pkg/attestation"
"google.golang.org/grpc/credentials"
)
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error) {
err := config.ReadAttestationPolicy(cfg.AttestationPolicy, &config.AttestationPolicy)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
}
var insecureSkipVerify bool = true
var rootCAs *x509.CertPool = nil
if len(cfg.ServerCAFile) > 0 {
insecureSkipVerify = false
// Read the certificate file
certPEM, err := os.ReadFile(cfg.ServerCAFile)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("failed to read certificate file"), err)
}
// Decode the PEM block
block, _ := pem.Decode(certPEM)
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
// Parse the certificate
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errors.Wrap(fmt.Errorf("failed to parse certificate"), err)
}
rootCAs = x509.NewCertPool()
rootCAs.AddCert(cert)
}
tlsConfig := &tls.Config{
InsecureSkipVerify: insecureSkipVerify,
RootCAs: rootCAs,
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPeerCertificateATLS(rawCerts, verifiedChains, cfg)
},
}
return credentials.NewTLS(tlsConfig), nil
}
func CustomDialer(ctx context.Context, addr string) (net.Conn, error) {
ip, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("could not create a custom dialer")
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, fmt.Errorf("bad format of IP address: %v", err)
}
conn, err := atls.DialTLSClient(ip, p)
if err != nil {
return nil, fmt.Errorf("could not create TLS connection")
}
return conn, nil
}
func verifyPeerCertificateATLS(rawCerts [][]byte, verifiedChains [][]*x509.Certificate, cfg AgentClientConfig) error {
if len(cfg.ServerCAFile) > 0 {
return nil
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return errors.Wrap(errCertificateParse, err)
}
err = checkIfCertificateSelfSigned(cert)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
return nil
}
func checkIfCertificateSelfSigned(cert *x509.Certificate) error {
certPool := x509.NewCertPool()
certPool.AddCert(cert)
opts := x509.VerifyOptions{
Roots: certPool,
CurrentTime: time.Now(),
}
if _, err := cert.Verify(opts); err != nil {
return err
}
return nil
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
//go:build !cgo
package grpc
import (
"fmt"
"google.golang.org/grpc/credentials"
)
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error) {
return nil, fmt.Errorf("aTLS is not supported without CGO. Please rebuild with CGO_ENABLED=1")
}
+16 -8
View File
@@ -19,6 +19,7 @@ import (
"github.com/google/go-sev-guest/proto/check"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
att "github.com/ultravioletrs/cocos/pkg/attestation"
)
func TestNewClient(t *testing.T) {
@@ -200,8 +201,9 @@ func TestClientSecure(t *testing.T) {
}
func TestReadAttestationPolicy(t *testing.T) {
validJSON := `{"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
validJSON := `{"pcr_values":{"sha256":{"0":"123"},"sha384":{"0":"123"}},"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
invalidJSON := `{"invalid_json"`
invalidJSONPCR := `{"pcr_values":{"sha256":{"0":true},"sha384":{"0":"123"}},"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
cases := []struct {
name string
@@ -219,19 +221,25 @@ func TestReadAttestationPolicy(t *testing.T) {
name: "Invalid JSON",
manifestPath: "invalid_manifest.json",
fileContent: invalidJSON,
err: ErrAttestationPolicyDecode,
err: att.ErrAttestationPolicyDecode,
},
{
name: "Non-existent file",
manifestPath: "nonexistent.json",
fileContent: "",
err: errAttestationPolicyOpen,
err: att.ErrAttestationPolicyOpen,
},
{
name: "Empty manifest path",
manifestPath: "",
fileContent: "",
err: ErrAttestationPolicyMissing,
err: att.ErrAttestationPolicyMissing,
},
{
name: "Invalid JSON PCR",
manifestPath: "invalid_manifest.json",
fileContent: invalidJSONPCR,
err: att.ErrAttestationPolicyDecode,
},
}
@@ -243,13 +251,13 @@ func TestReadAttestationPolicy(t *testing.T) {
defer os.Remove(tt.manifestPath)
}
config := check.Config{}
err := ReadAttestationPolicy(tt.manifestPath, &config)
config := att.Config{Config: &check.Config{}, PcrConfig: &att.PcrConfig{}}
err := att.ReadAttestationPolicy(tt.manifestPath, &config)
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
if tt.err == nil {
assert.NotNil(t, config.Policy)
assert.NotNil(t, config.RootOfTrust)
assert.NotNil(t, config.Config.Policy)
assert.NotNil(t, config.Config.RootOfTrust)
}
})
}
@@ -1,26 +1,20 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"os"
"strconv"
"time"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/proto/check"
"github.com/ultravioletrs/cocos/pkg/atls"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/encoding/protojson"
)
type security int
@@ -41,9 +35,6 @@ const (
var (
errGrpcConnect = errors.New("failed to connect to grpc server")
errGrpcClose = errors.New("failed to close grpc connection")
errAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
errCertificateParse = errors.New("failed to parse x509 certificate")
errAttVerification = errors.New("certificat is not sefl signed")
errFailedToLoadClientCertKey = errors.New("failed to load client certificate and key")
@@ -59,7 +50,7 @@ type BaseConfig struct {
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
ClientCert string `env:"CLIENT_CERT" envDefault:""`
ClientKey string `env:"CLIENT_KEY" envDefault:""`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
}
type AgentClientConfig struct {
@@ -89,13 +80,8 @@ func (a CVMClientConfig) GetBaseConfig() BaseConfig {
}
type Client interface {
// Close closes gRPC connection.
Close() error
// Secure is used for pretty printing TLS info.
Secure() string
// Connection returns the gRPC connection.
Connection() *grpc.ClientConn
}
@@ -124,7 +110,6 @@ func (c *client) Close() error {
if err := c.ClientConn.Close(); err != nil {
return errors.Wrap(errGrpcClose, err)
}
return nil
}
@@ -136,8 +121,6 @@ func (c *client) Secure() string {
return "with mTLS"
case withaTLS:
return WithATLS
case withoutTLS:
fallthrough
default:
return "without TLS"
}
@@ -147,25 +130,19 @@ func (c *client) Connection() *grpc.ClientConn {
return c.ClientConn
}
// connect creates new gRPC client and connect to gRPC server.
func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
opts := []grpc.DialOption{
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
}
secure := withoutTLS
var tc credentials.TransportCredentials
if agcfg, ok := cfg.(AgentClientConfig); ok && agcfg.AttestedTLS {
err := ReadAttestationPolicy(agcfg.AttestationPolicy, &quoteprovider.AttConfigurationSEVSNP)
tc, err := setupATLS(agcfg)
if err != nil {
return nil, secure, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
return nil, secure, err
}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateATLS,
}
tc = credentials.NewTLS(tlsConfig)
opts = append(opts, grpc.WithTransportCredentials(tc))
opts = append(opts, grpc.WithContextDialer(CustomDialer))
secure = withaTLS
} else {
@@ -174,13 +151,11 @@ func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
if err != nil {
return nil, secure, err
}
tc = transportCreds
opts = append(opts, grpc.WithTransportCredentials(transportCreds))
secure = sec
}
opts = append(opts, grpc.WithTransportCredentials(tc))
conn, err := grpc.NewClient(cfg.GetBaseConfig().URL, opts...)
conn, err := grpc.Dial(cfg.GetBaseConfig().URL, opts...)
if err != nil {
return nil, secure, errors.Wrap(errGrpcConnect, err)
}
@@ -192,7 +167,6 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
secure := withoutTLS
tc := insecure.NewCredentials()
// Load Root CA certificates
if serverCAFile != "" {
rootCA, err := os.ReadFile(serverCAFile)
if err != nil {
@@ -209,7 +183,6 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
}
}
// Load mTLS certificates
if clientCert != "" || clientKey != "" {
certificate, err := tls.LoadX509KeyPair(clientCert, clientKey)
if err != nil {
@@ -222,69 +195,3 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
return tc, nil, secure
}
func ReadAttestationPolicy(manifestPath string, attestationConfiguration *check.Config) error {
if manifestPath != "" {
manifest, err := os.ReadFile(manifestPath)
if err != nil {
return errors.Wrap(errAttestationPolicyOpen, err)
}
if err := protojson.Unmarshal(manifest, attestationConfiguration); err != nil {
return errors.Wrap(ErrAttestationPolicyDecode, err)
}
return nil
}
return ErrAttestationPolicyMissing
}
func CustomDialer(ctx context.Context, addr string) (net.Conn, error) {
ip, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("could not create a custom dialer")
}
p, err := strconv.Atoi(port)
if err != nil {
return nil, fmt.Errorf("bad format of IP address: %v", err)
}
conn, err := atls.DialTLSClient(ip, p)
if err != nil {
return nil, fmt.Errorf("could not create TLS connection")
}
return conn, nil
}
func verifyPeerCertificateATLS(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return errors.Wrap(errCertificateParse, err)
}
err = checkIfCertificateSelfSigned(cert)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
return nil
}
func checkIfCertificateSelfSigned(cert *x509.Certificate) error {
certPool := x509.NewCertPool()
certPool.AddCert(cert)
opts := x509.VerifyOptions{
Roots: certPool,
CurrentTime: time.Now(),
}
if _, err := cert.Verify(opts); err != nil {
return err
}
return nil
}

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