Compare commits

..

27 Commits

Author SHA1 Message Date
Washington Kigani Kamadi d76074ae41 COCOS-144 - Add Agent SDK Tests (#167)
* add tests and mocks

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

fix ci

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

update test

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

fix(agent/grpc): revert change

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

fix ci

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

* refactor attestation and report tests

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

refactor tests

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

remove commented code

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

remove comment

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

remove comments

* add test cases

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

export agent errors

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

remove comm

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

* fix tests

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

---------

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2024-07-11 20:53:31 +02:00
Sammy Kerata Oina 2ceb1c3562 COCOS-152 - Refactor algorithm execution and add visibility through logging and events (#176)
* * feat(agent): add support for binary algorithm execution
*
* feat(agent/algorithm): add Algorithm interface and binary implementation
*
* feat(agent/algorithm/binary): implement Run method for binary algorithm execution
*
* feat(agent/algorithm/logging): implement Stdout and Stderr writers for algorithm logging
*
* feat(agent/algorithm/logging_test): add tests for Stdout and Stderr writers
*
* feat(agent/events): add Service interface for sending events
*
* feat(agent/events/mocks): add mock implementation for Service interface
*
* refactor(agent/service): update runComputation method to use binary algorithm implementation

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

* * fix(logging.go): handle error when sending event in Write method of Stderr struct
* test(logging_test.go): add copyright header
* fix(backend_info.go): add missing type declaration in function signature
* fix(agent.go): rename progressbar variable to pb for clarity and consistency

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-07-11 12:24:19 +02:00
b1ackd0t dc16e8a997 NOISSUE - Use Magistrala Shared Packages (#174)
* refactor(env): remove internal env package

No need for this package since the parent package github.com/caarlos0/env does everything we need

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>

* refactor(jaeger): remove internal jaeger package

No need for this package since we can use magistrala exported package

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>

* refactor(metrics): remove internal metrics pkg

Use exported magistrala prometheus package

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>

* chore(dep): Update grpc and other dependencies

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>

* style(linter): remove enabled by default linters

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>

---------

Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
2024-07-10 15:48:37 +02:00
Sammy Kerata Oina f4e3e8e09c COCOS-157 - Provide abstractions for VM management (#171)
* abstract vm creation and allow stopping computation

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

* Refactor QEMU configuration loading and execution in main.go

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

* * feat(agent-config): add support for sending agent configuration to manager

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

* * chore(checkproto.yaml): update protoc-gen and protoc-grpc versions

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

* * chore(auth): update mockery version to v2.43.2
* chore(main.go): update import path for vm package in agent
* chore(main.go): update import path for vm package in manager
* chore(go.mod): add github.com/google/logger v1.1.1 as a required dependency
* chore(manager_test.go): update import path for vm package in manager
* chore(logging.go): move logging.go to manager/qemu/vm package
* chore(logging_test.go): move logging_test.go to manager/qemu/vm package
* chore(vm_factory.go): rename vm_factory.go to provider.go in manager/qemu/vm/mocks package
* chore(vm.go): move vm.go to manager/qemu/vm package
* chore(vm.go): update import path for vm package in manager
* chore(vm_test.go): move vm_test.go to manager/qemu/vm package
* chore(vsock.go): move vsock.go to manager

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

* * fix(main.go): change import path for 'github.com/ultravioletrs/cocos/manager/qemu/vm' to 'github.com/ultravioletrs/cocos/manager/vm'
* fix(main.go): change vsock.Dial argument from 'vm.VsockConfigPort' to 'qemu.VsockConfigPort'
* fix(main.go): change import path for 'github.com/ultravioletrs/cocos/manager/qemu' to 'github.com/ultravioletrs/cocos/manager/qemu'

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-07-10 15:33:54 +02:00
Sammy Kerata Oina d1c9834d86 NOISSUE - Fix user role on RPC call (#173)
* * fix(interceptors.go): change role from auth.DataProviderRole to auth.ConsumerRole in AuthenticateUser call

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

* * test(interceptors_test.go): update role variable values in TestAuthUnaryInterceptor
* - Change role value from auth.DataProviderRole to auth.ConsumerRole in the "authorized result method" test case
* - Change role value from auth.DataProviderRole to auth.ConsumerRole in the "unauthorized result method" test case
* - Change role value from auth.DataProviderRole to auth.ConsumerRole in the "other method" test case

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-07-10 13:42:41 +02:00
Danko Miladinovic 654be60a80 COCOS-132 - Add progress bar for algo and data uploads (#162)
* add progress bar to CLI

* fix error handling

* fix comments errors

* add header

* add wraper for AlgoClient and DataClient

* add compile time check for wrapper structs

* refactor code
2024-07-09 09:35:40 +02:00
Danko Miladinovic 006897a57c COCOS-153 - Add host-data option (#163)
* add host_data option

* add CLI hostdata option and rename platform to backend

* move code for computation hash to a function

* rename getComputationHash to computationHash

* add default for backend information field switch
2024-07-08 15:32:13 +02:00
Sammy Kerata Oina 0e236bf2e8 * feat(hal/linux): add support for wasmtime package (#166)
* fix(hal/linux/Config.in): add source for wasmtime Config.in file

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-07-04 17:04:51 +02:00
Washington Kigani Kamadi ff5bade57d COCOS-145 : Support Other Private/Public Key Pair Algorithms (#147)
* feat: generate multiple key types using cli

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

pass cert to agent

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

fix ci

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

standardize key types

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

marshal public key

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

use openssl format for keys

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

* update auth

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

update makefile

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

update agent

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

enhance parsing logic

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

* pass key type as flag

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

fix ci

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

return key on successful signature verification

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

fix cli

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

fix ed25519 signing

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

ci

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

* refactor code

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

* fix(cli/result.go): dry code

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

---------

Signed-off-by: WashingtonKK <washingtonkigan@gmail.com>
2024-07-03 22:56:18 +02:00
Danko Miladinovic 7c090fd19f NOISSUE - Add CLI option to download ASK and ARK (#149)
* add cli option to download ask and ark

* add ARK and ASK to cert chain of the attestation report

* fix spelling mistake

* add explanation for aTLS testing

* remove commented code
2024-07-03 16:07:19 +02:00
Sammy Kerata Oina 30092c6a45 COCOS-135 - Refactor gRPC auth interceptors and update dependencies (#140)
* Implement gRPC auth interceptors and update dependencies

Introduced gRPC authentication interceptors for unary and stream calls in the agent's API to enhance security. These interceptors verify signatures based on roles before handling requests, rejecting unauthorized ones. The authentication logic was refactored, adding relevant error handling.

Added mocks for authentication interfaces to facilitate testing and replaced direct errors with wrapped ones to provide additional context.

The Makefile now includes a version pin for the Mockery tool, ensuring consistent mock generation across environments.

Updated the gRPC server setup in `grpc.go` to utilize the new interceptors. This change aims to reinforce the API's access control by authenticating users according to their roles and is expected to prevent unauthorized access to sensitive endpoints.

Lastly, dependencies in go.mod and go.sum were updated, most notably the inclusion of testify for improved testing capabilities.

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

* fix linting errors

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

* * chore(Makefile): remove unused variables and update 'mocks' target

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-07-01 21:39:35 +02:00
Sammy Kerata Oina 617b595559 Add Docker support to system configuration (#164)
Added support for Docker in the system configuration file. This includes packages like libseccomp, CA certificates, Docker CLI, Docker Compose, Docker Engine, containerd, and runc.

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-07-01 21:37:58 +02:00
Sammy Kerata Oina 4c4161ccfb Fix capitalized Vnc field and increment Vnc on run (#139)
Corrected the struct field 'vnc' to 'Vnc' to match Go's public field naming conventions and to make it exportable. Each QEMU virtual machine's VNC display number now increments upon instantiation to avoid display conflicts.

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-06-06 16:34:13 +02:00
Sammy Kerata Oina ff64c10ac3 NOISSUE - Add new key management command to CLI (#136)
Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-05-29 13:09:54 +02:00
Danko Miladinovic 0574abc228 NOISSUE - Add Rust script for fetching platform data (#133)
* add rust program for fetching platform data

* fix new line error

* add CLI options to add the measurement to platform_info.json file

* add documentation for platform info testing

* add explanation for sev-snp-measure

* delete excess space

* fix minor errors

* fix minor errors

* add file permision constant
2024-05-28 18:08:07 +02:00
Sammy Kerata Oina 2ce112cc1b COCOS-103 - User authN and AuthZ using digital signatures (#128)
* Update Go to 1.22 and enhance security features

- Upgraded the Go version in GitHub Actions workflows to 1.22.x for latest features and security patches.
- Added RSA public key field `UserKey` in `Dataset` and `Algorithm` to reinforce data integrity and encryption.
- Refactored `Result` method in `agentService` to use `containsID` for improved readability and potential performance benefits.
- Updated `grpcserver.New` and `internal/server/grpc` invocations to pass `agent.Service` by value in line with recommended Go practices.
- Introduced `grpc.StreamInterceptor` with no args in `Server.Start` which seems to be an initial step for future stream interceptor configuration.

These changes prepare for stronger data security measures, maintain compatibility with the latest Go features, and improve code quality regarding service struct usage. Potential follow-up is needed to configure the stream interceptor and to ensure the new RSA key field is appropriately utilized in data handling.

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

* Refactor auth system and protocol buffers

Enhanced the authentication system by adding context support and an improved user-role model. Implemented robust RSA public key verification for users and a restructured interceptor logic specific to stream types, streamlining the auth process. Updated protocol buffers and associated structures to accommodate user keys as byte slices, aligning with standard cryptographic practice. CLI commands for algorithms and datasets now require a private key file path argument for signing, strengthening security during interactions.

This comprehensive overhaul addresses security and efficiency considerations in the RPC framework and aligns with best practices for key handling. By streamlining and securing the user authentication process, the agent service's reliability is greatly improved, directly impacting the robustness of the entire computation pipeline.

- Refactored auth: added role-based user validation, context handling
- Reworked interceptors: separated stream types, fortified signature checks
- Updated protocol buffers: user public keys as byte slices for standard compatibility
- Enhanced CLI: introduced private key argument, ensuring secure algorithm and dataset submission
- Improved server and SDK contracts to align with auth changes

Related issues:
- Implements user roles and auth context [#103]
- CLI security enhancement for private key management

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

* Updated PEM decoding for key parsing in CLI and tests

Added `encoding/pem` to decode PEM blocks when parsing private and public keys across CLI commands and test computation scenarios, ensuring compatibility with key files. This enhances robustness in key handling by supporting PEM encoded keys. The update also includes registration of a new Keys command in the CLI.

Refactored code is now compliant with common key formats, addressing potential parsing issues.

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

* Fix auth signature encoding and improve CLI usage example

The authentication system now decodes base64 strings before verifying signatures to align with the expected format. Additionally, the signature generation now encodes the output in base64, ensuring consistency across the auth process.

The CLI help message for the `result` command is enhanced by providing a usage example, making it more user-friendly and informative.

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

* Refactor containsID to handle dynamic fields

Updated the `containsID` function to accept a field name parameter, enabling dynamic field lookup within the reflection logic. This change facilitates the use of the function for various struct fields, improving code reusability and flexibility.

CLI command 'data' now requires an additional argument for the private key file path, outlined in the usage example update, reinforcing command clarity and user guidance.

Resolves issues with hardcoded field lookups and enhances CLI usability.

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

* Remove extraneous newline in key generation log output

A redundant newline after the success message in the key generation command was removed to clean up log output formatting. This change ensures a more consistent and professional appearance of the CLI tool's messages.

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

* Implemented auth service in gRPC startup

Added authentication services to the gRPC server initialization to enforce security measures. The gRPC server's New function now includes an `authSvc` parameter, requiring instantiation of the auth service before starting the server. Failure to create the auth service results in a fatal error, halting the process to avoid running without protection. Tests have been updated to include `nil` values for the auth service parameter to maintain their functionality without authentication.

Refactored `grpcserver.New` to accept the new auth service, and updated the main agent startup logic to create and inject the auth service. Added the auth middleware interceptors to the server options, which ensures that each gRPC call will undergo authentication.

This change is a step towards secure communication, and affected components should now consider the authentication requirement.

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

* Refactor config read logic and update agent setup

Improved the configuration reading in `cmd/agent/main.go` to handle larger payloads by reading data in chunks and checking for EOF, ensuring that all config data is captured even if it exceeds the initial buffer size. Enhanced the `test/manual/agent-config/main.go` to require additional command-line arguments, improving the setup process by explicitly requiring paths for data, algorithm, and public key as well as a boolean for attested TLS. Also updated the hashing method to SHA3 for the algorithm and data files, and included the hash and public keys as part of the agent, dataset, and result consumer configurations. These changes will make the agent setup more robust and provide better integrity checks for the involved files.

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

* Refactor run method to agentService

Moved the run function into agentService for better encapsulation and maintainability. This refactoring includes capturing both stdout and stderr during algorithm execution, enabling more informative debugging through enhanced logging. Consequentially, the run method now references members through the service instance, aligning with object-oriented best practices and improving code coherence.

Resolves issue with insufficient execution details when computations fail.

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

* Refactor computation data handling to use filepaths

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

* Refactor error logging and ensure consistency

Replaced usage of the standard log package with a custom logger for error reporting to standardize error logging throughout the application. Additionally, introduced graceful shutdown by returning from the main function rather than forcing exit when failing to create auth service, aligning the application's error handling strategy.

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

* Refactor auth initialization and key file handling

Improved the readability and maintainability of the authentication service initialization by adding line breaks for logical separation. Also, standardized key filenames in the CLI key generation by introducing constants, enhancing code clarity and reducing the likelihood of file-naming errors.

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

* Refactor auth verification logic for improved security

Removed an extraneous line in the `verifySignature` function that was not necessary for the signature verification process. This change simplifies the code and improves readability.

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

* Refactor payload structures to simplify API

Removed the 'provider', 'id', 'consumer' fields from protocol buffers, gRPC services, and related functions across various files to streamline the data model and align with the new authentication system based on cryptographic verification rather than string identifiers. This results in more efficient data handling and a reduction in unnecessary payload data, while enhancing security by making entity validation strictly cryptographic.

The changes affect agent-SDK interactions, CLI tools, and related services, ensuring only the necessary data (algorithm/data bytes, user keys, and hashes) is transmitted and processed. Consequently, the core computation algorithm and dataset handlers now rely on indexes derived from context to associate data with respective manifest entries, thus maintaining the ability to link to specific computation manifests without relying on explicit IDs in the payload. Additionally, refactored authentication methods now enforce role-based security seamlessly through metadata.

This approach enhances privacy by avoiding transmission of potentially sensitive strings over the network and by ensuring that only internal indices, not globally interpretable identifiers, are used to process computations.

Aligned with the broader architectural goal of simplifying and securing the platform's core services, this change paves the way for upcoming revisions to the authentication scheme that will further consolidate role-based security and improve system integrity.

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

* Enhance CLI security with key paths

Removed the section on running computations from the CLI README as it may no longer be necessary or the functionality has been moved elsewhere. Required private key file paths for algorithm, dataset upload, and result retrieval commands to enhance security. This change associates each action with a specific identity, ensuring secure and traceable operations. Additionally, updated the manual test commands to reflect this new requirement.

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

* fix ci

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

* fix fmt

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-05-28 14:10:13 +02:00
Sammy Kerata Oina aebe01a873 NOISSUE - Updated README with static binary instructions (#130)
Enhanced the manual algorithm testing documentation by including steps to install additional dependencies and guidance on creating static binaries. This ensures wider compatibility and ease of deployment for python programs tested with this framework.

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-05-22 14:40:34 +02:00
Sammy Kerata Oina 4c681569f1 NOISSUE - Add sha3-256 file hashing command to CLI (#129)
Introduced a new command for the CLI to calculate the sha3-256 hash of a given file. The update enhances the utility's cryptography toolkit by allowing users to easily generate and verify file integrity directly through the command-line interface.

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-05-20 16:56:42 +02:00
Sammy Kerata Oina c274521faf Refactor computation data handling to use filepaths (#126)
Changed the internal representation of algorithms and datasets within the service from byte slices to file paths, writing received data directly to temp files. This modification allows for handling potentially large data sets without the need to load them entirely into memory, improving the memory efficiency and scalability of the service. Additionally, it aligns the call signature of external algorithms with the new approach, updating documentation and examples accordingly. Updated the linear regression example for consistency with the new data handling process.

Resolves issues with memory bloat when processing large datasets.

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-05-13 17:16:39 +02:00
Sammy Kerata Oina 226704cf0d COCOS-122 - Enable streaming RPCs for Algo and Data services (#123)
* Enable streaming RPCs for Algo and Data services

Modified the gRPC service definitions for Algo and Data methods to use stream processing, enabling the handling of larger datasets and algorithms without being limited by memory restrictions. This allows client and server to send chunks of data sequentially rather than requiring the entire payload to be loaded into memory at once.

Updated server implementations to accumulate data from multiple chunks, allowing for more efficient processing and communication when dealing with large files. Client implementations have been adjusted to segment and send data in a streaming fashion.

Removed previously existing synchronous client code as it became redundant with the new streaming approach, streamlining the client's communication patterns with the gRPC backend.

This change allows for better resource management, especially in systems with constraints on memory, improving overall scalability and performance of the data and algorithm processing pipeline.

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

* Refactor algorithm ID check logic

Simplify the algorithm validation logic in the agent service by replacing the previous containment check with direct ID comparison. This change streamlines the error handling for undeclared algorithms and hash mismatches, while also ensuring clear and direct provider validation. The modifications enhance the readability and maintainability of the code without altering functionality.

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

* Updated README to build single-file executable with PyInstaller

Modified the PyInstaller command in the manual testing README to bundle the linear regression script into a single executable file. This simplifies distribution and execution of the script by eliminating the need for multiple dependency files.

Ref: Optimization of deployment process
Signed-off-by: SammyOina <sammyoina@gmail.com>

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-05-13 13:14:50 +02:00
Danko Miladinovic 479598a663 COCOS-116 -Separated networking setup from agent start up script (#118)
* added network setup script

* removed fixed name enp0s2 from code

* fixed small typo

* changed the capitalization of a letter
2024-05-01 17:03:28 +02:00
Sammy Kerata Oina 8d082567d7 COCOS-101 - Switch to self-contained algorithms as executables (#119)
* Switch to self-contained algorithms as executables

Transitioned from using Python scripts to self-contained binary executables for running algorithms, improving modularity and reducing dependencies. This change removes the reliance on a Python environment, as evident by the removal of Python setup and packages from the build configuration. The service now creates temporary executable files for algorithm runs, handling all permissions and cleanup, enhancing security and maintaining clean execution states. A warning is logged if computation fails, aiding in debugging. Additionally, updated manual tests to reflect these changes in the agent's handling of algorithms.

Refactors:
- Removed Python runtime const since it's no longer needed.
- Updated documentation and test commands to reflect the change from .py to .bin for algorithm files.

Build config:
- Removed Python and pip packages to reduce the build size and complexity.

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

* Update agent service.go file with new constants and file permission

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

* Refine singular usage of 'algorithm' across modules

Standardized terminology throughout the project to refer to 'algorithm' in the singular form rather than plural. Streamlined various documentations, string constants, function names, and variable names to bring cohesiveness and eliminate ambiguity when handling algorithms across README files, CLI interfaces, and internal API representations.

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

* Fix state names and indices in state_string.go

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-04-29 15:48:17 +02:00
Sammy Kerata Oina 4b5000d107 NOISSUE - Allow termination of manager (#95)
* Implement manager client heartbeat mechanism

Introduced a heartbeat system in the manager service to maintain an active link with client agents. The updates involve sending periodic heartbeat signals controlled via the new environment variable `MANAGER_HEARTBEAT_INTERVAL`, defaulting to 1 second. The protobuf definitions, service interfaces, and server logic have been updated accordingly to handle the incoming signals. This change allows better tracking of active client connections and could be used for features like auto-reconnect or resource cleanup for lost connections in the future.

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

* Enhance gRPC server to handle client termination

Introduce the capability for the gRPC server in the manager module to process termination requests from clients. Server and client proto buffers have been updated to include a Termination message, allowing clients to signal the server to stop processing. Handling for communication and termination flows involving context cancellation have been added across server implementation, manager client, and main execution logic to properly respond to termination signals. This ensures clean shutdowns and resource deallocation when a client requests termination, improving the robustness of the system.

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

* Refactor gRPC server context handling

Removed the redundant storage of context in grpcServer struct and modified the NewServer constructor to exclude the context parameter. Updated the Process method to derive context from the stream directly, ensuring better context scoping and adherence to proper gRPC patterns. This change simplifies the server's context management and ensures contexts are more accurately associated with their corresponding streams, which can improve debugging and request cancellation behavior.

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

* Update protoc to v4.25.3 and remove heartbeat system

Upgraded protoc version to 4.25.3, ensuring compatibility with the latest protocol buffer features and potential performance improvements. Additionally, removed the entire heartbeat system, involving both its gRPC server implementation and associated client-side logic. This elimination suggests a shift in the service health-check strategy, potentially to a more modern or efficient model.

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

* Removed heartbeat functionality for manager service

The heartbeat feature and its related configuration have been removed across the application. This includes the removal of the Heartbeat method from the Service interface, the associated interval setting in the config structure, and related environment variable documentation. The change implies an architectural shift towards alternative methods of service availability checking or potentially a move to stateless service design. The cleanup removes unnecessary code, thus simplifying the overall service logic.

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

* Introduce Terminate message and streamline gRPC server logic

- Added a new `Terminate` protobuf message to handle termination requests more explicitly.
- Removed redundant context cancels in gRPC server stream processing to rely on context propagation.
- Updated channel usage to match the altered stream message types; this aligns message handling with the updated protobuf definitions.
- Passed client authentication info to service run function to enhance the security during service execution.
- Utilized stream's context directly in error groups for improved clarity and error handling scope.
- Refactored message descriptors in protobuf to maintain correct ordering after introducing new message types.

These changes enhance the codebase's maintainability, simplify the logic, and align with the updated messaging protocol.

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

* Update agent configuration in main.go

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-04-29 14:08:42 +02:00
Sammy Kerata Oina 64f7e7f7fd NOISSUE - Refactor single algorithm processing (#117)
* Refactor single algorithm processing

Simplified the agent service's algorithm handling logic to process a single algorithm instead of multiple. This change:
- Removed the `Algorithms` type and associated stringer implementation.
- Updated the state machine and service logic to expect a singular algorithm, aligning the agent's internal state transitions with the new model.
- Adjusted the manager service and computations test server to mirror these changes in their respective payload structures, ensuring API and test consistency.
- Altered README files to reflect the simplified interaction model and removed outdated descriptions.
- Reverted the protoc-gen-go version used for generating protobuf files to maintain compatibility with the rest of the codebase.

The single-algorithm approach streamlines the computation running process, reducing complexity and potential error conditions. It directly impacts how external services will construct and send computation requests.

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

* Update protoc-gen-go version to v1.33.0

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

* Refactor variable name in computations.go and grpc.go

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

---------

Signed-off-by: SammyOina <sammyoina@gmail.com>
2024-04-17 13:34:54 +02:00
Danko Miladinovic 3a14896555 NOISSUE - Attested TLS (#99)
* added initial code for attested TLS

* added client validation and verification

* fixed bugs for attested TLS

* updated README for manual testing

* fixed CI errors

* removed SNP pollicy from agent config

* added attested TLS config param to AgentConfig

* generated manager.pb.go for protoc v25.2

* updated proto-gen-go version on CI

* generated agent.pb.go to match newest proto gen version

* define errors for error handling

* fixed comments
2024-04-16 15:19:19 +02:00
Drasko DRASKOVIC b57bafd310 Fix usage (#114)
Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>
2024-04-16 00:42:47 +02:00
Drasko DRASKOVIC 9e6fb31d90 Fix links and add features (#113)
Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>
2024-04-15 22:26:41 +02:00
101 changed files with 5242 additions and 1550 deletions
+4 -4
View File
@@ -29,13 +29,13 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.21.x
go-version: 1.22.x
- name: Set up protoc
run: |
PROTOC_VERSION=25.3
PROTOC_GEN_VERSION=v1.31.0
PROTOC_GRPC_VERSION=v1.3.0
PROTOC_VERSION=27.2
PROTOC_GEN_VERSION=v1.34.2
PROTOC_GRPC_VERSION=v1.4.0
# Download and install protoc
PROTOC_ZIP=protoc-$PROTOC_VERSION-linux-x86_64.zip
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.21.x
go-version: 1.22.x
cache-dependency-path: "go.sum"
- name: Checkout cocos
+3 -3
View File
@@ -19,12 +19,12 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.21.x
go-version: 1.22.x
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: latest
version: v1.59.1
- name: Build
run: |
+2
View File
@@ -6,3 +6,5 @@ cmd/manager/iso
cmd/manager/tmp
.cov
*.pem
+2 -8
View File
@@ -1,6 +1,6 @@
run:
timeout: 3m
issues:
max-issues-per-linter: 10
max-same-issues: 10
@@ -15,10 +15,7 @@ linters-settings:
gocritic:
enabled-checks:
- captLocal
- dupImport
- singleCaseSwitch
- switchTrue
- importShadow
- httpNoBody
- paramTypeCombine
@@ -34,9 +31,6 @@ linters-settings:
- style
- experimental
- opinionated
misspell:
ignore-words:
- "mosquitto"
stylecheck:
checks: ["-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022"]
goheader:
@@ -83,4 +77,4 @@ linters:
- makezero
- mirror
- nakedret
- dupword
- dupword
+8 -3
View File
@@ -1,12 +1,11 @@
BUILD_DIR = build
SERVICES = manager agent cli
PLATFORM_INFO = platform_info
CGO_ENABLED ?= 0
GOARCH ?= amd64
VERSION ?= $(shell git describe --abbrev=0 --tags --always)
COMMIT ?= $(shell git rev-parse HEAD)
TIME ?= $(shell date +%F_%T)
empty:=
space:= $(empty) $(empty)
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
@@ -17,13 +16,19 @@ define compile_service
-o ${BUILD_DIR}/cocos-$(1) cmd/$(1)/main.go
endef
.PHONY: all $(SERVICES)
.PHONY: all $(SERVICES) $(PLATFORM_INFO)
all: $(SERVICES)
$(SERVICES):
$(call compile_service,$(@))
$(PLATFORM_INFO):
$(MAKE) -C ./scripts/platform_info
protoc:
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/agent.proto
protoc -I. --go_out=./pkg --go_opt=paths=source_relative --go-grpc_out=./pkg --go-grpc_opt=paths=source_relative manager/manager.proto
mocks:
go generate ./...
+17 -8
View File
@@ -7,7 +7,7 @@ based on the [Confidential Computing][cc] and [Trusted Execution Environments (T
</p>
With Cocos AI it becomes possible to run AI/ML workloads on combined datasets from multiple organizations
while guaranteeing the privacy and security of the data and the algorithms.
while guaranteeing the privacy and security of the data and the algorithm.
Data is always encrypted, protected by hardware secure enclaves (Trusted Execution Environments),
attested via secure remote attestation protocols, and invisible to cloud processors or any other
3rd party to which computation is offloaded.
@@ -26,20 +26,28 @@ Cocos AI is implementing the following features:
## Usage
The quickest way to start using Cocos is via the CLI. The latest version can be downloaded from the [official releases page][rel].
It can also be built and used from the project's root directory:
Clone the repo and create binaries:
```bash
make cli
./build/cocos-cli version
git clone git@github.com:ultravioletrs/cocos.git
make
```
Additional details on using the CLI can be found in the [CLI documentation][cli].
This will create 3 binaries:
```bash
ls build/
# cocos-agent cocos-cli cocos-manager
```
- Manager can be deployed on the AMD SEV-SNP host
- Agent can be built into [EOS][eos]-based HAL
- CLI can be used to communicate to remote Agent.
## Documentation
Official documentation is hosted at [Cocos official docs page][docs]. Documentation is auto-generated, checkout the instructions on [official docs repository](https://github.com/ultravioletrs/docs).
Project documentation is hosted at [Cocos AI official docs page][docs].
Documentation is generated from the [docs repository](https://github.com/ultravioletrs/docs).
## License
Cocos AI is published under permissive open-source [Apache-2.0](LICENSE) license.
@@ -50,3 +58,4 @@ Cocos AI is published under permissive open-source [Apache-2.0](LICENSE) license
[tee]: https://en.wikipedia.org/wiki/Trusted_execution_environment
[docs]: https://docs.cocos.ultraviolet.rs
[cli]: https://docs.cocos.ultraviolet.rs/cli
[eos]: https://github.com/ultravioletrs/eos
-1
View File
@@ -15,7 +15,6 @@ The service is configured using the environment variables from the following tab
| 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 | "" |
| COCOS_NOTIFICATION_SERVER_URL | Server to receive notification events from agent. | http:/localhost:9000 |
## Deployment
+36 -84
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v4.25.3
// protoc-gen-go v1.34.2
// protoc v5.27.2
// source: agent/agent.proto
package agent
@@ -29,8 +29,6 @@ type AlgoRequest struct {
unknownFields protoimpl.UnknownFields
Algorithm []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
}
func (x *AlgoRequest) Reset() {
@@ -72,20 +70,6 @@ func (x *AlgoRequest) GetAlgorithm() []byte {
return nil
}
func (x *AlgoRequest) GetProvider() string {
if x != nil {
return x.Provider
}
return ""
}
func (x *AlgoRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
type AlgoResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -129,9 +113,7 @@ type DataRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
}
func (x *DataRequest) Reset() {
@@ -173,20 +155,6 @@ func (x *DataRequest) GetDataset() []byte {
return nil
}
func (x *DataRequest) GetProvider() string {
if x != nil {
return x.Provider
}
return ""
}
func (x *DataRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
type DataResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -229,8 +197,6 @@ type ResultRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Consumer string `protobuf:"bytes,1,opt,name=consumer,proto3" json:"consumer,omitempty"`
}
func (x *ResultRequest) Reset() {
@@ -265,13 +231,6 @@ func (*ResultRequest) Descriptor() ([]byte, []int) {
return file_agent_agent_proto_rawDescGZIP(), []int{4}
}
func (x *ResultRequest) GetConsumer() string {
if x != nil {
return x.Consumer
}
return ""
}
type ResultResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -417,39 +376,32 @@ var File_agent_agent_proto protoreflect.FileDescriptor
var file_agent_agent_proto_rawDesc = []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, 0x57, 0x0a, 0x0b, 0x41, 0x6c,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x2b, 0x0a, 0x0b, 0x41, 0x6c,
0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67,
0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x61, 0x6c,
0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x02, 0x69, 0x64, 0x22, 0x0e, 0x0a, 0x0c, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x53, 0x0a, 0x0b, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e,
0x73, 0x75, 0x6d, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6e,
0x73, 0x75, 0x6d, 0x65, 0x72, 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, 0xf5, 0x01,
0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x31,
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, 0x12, 0x31, 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, 0x12, 0x37, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14,
0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x22, 0x0e, 0x0a, 0x0c, 0x41, 0x6c, 0x67, 0x6f, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x44, 0x61, 0x74, 0x61, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74,
0x22, 0x0e, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
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, 0xf9, 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, 0x37, 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, 0x12, 0x46, 0x0a,
@@ -474,7 +426,7 @@ func file_agent_agent_proto_rawDescGZIP() []byte {
}
var file_agent_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_agent_agent_proto_goTypes = []interface{}{
var file_agent_agent_proto_goTypes = []any{
(*AlgoRequest)(nil), // 0: agent.AlgoRequest
(*AlgoResponse)(nil), // 1: agent.AlgoResponse
(*DataRequest)(nil), // 2: agent.DataRequest
@@ -506,7 +458,7 @@ func file_agent_agent_proto_init() {
return
}
if !protoimpl.UnsafeEnabled {
file_agent_agent_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*AlgoRequest); i {
case 0:
return &v.state
@@ -518,7 +470,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*AlgoResponse); i {
case 0:
return &v.state
@@ -530,7 +482,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*DataRequest); i {
case 0:
return &v.state
@@ -542,7 +494,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*DataResponse); i {
case 0:
return &v.state
@@ -554,7 +506,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*ResultRequest); i {
case 0:
return &v.state
@@ -566,7 +518,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*ResultResponse); i {
case 0:
return &v.state
@@ -578,7 +530,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*AttestationRequest); i {
case 0:
return &v.state
@@ -590,7 +542,7 @@ func file_agent_agent_proto_init() {
return nil
}
}
file_agent_agent_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
file_agent_agent_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*AttestationResponse); i {
case 0:
return &v.state
+2 -7
View File
@@ -8,30 +8,25 @@ package agent;
option go_package = "./agent";
service AgentService {
rpc Algo(AlgoRequest) returns (AlgoResponse) {}
rpc Data(DataRequest) returns (DataResponse) {}
rpc Algo(stream AlgoRequest) returns (AlgoResponse) {}
rpc Data(stream DataRequest) returns (DataResponse) {}
rpc Result(ResultRequest) returns (ResultResponse) {}
rpc Attestation(AttestationRequest) returns (AttestationResponse) {}
}
message AlgoRequest {
bytes algorithm = 1;
string provider = 2;
string id = 3;
}
message AlgoResponse {}
message DataRequest {
bytes dataset = 1;
string provider = 2;
string id = 3;
}
message DataResponse {}
message ResultRequest {
string consumer = 1;
}
message ResultResponse {
+133 -60
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v4.25.3
// - protoc-gen-go-grpc v1.4.0
// - protoc v5.27.2
// source: agent/agent.proto
package agent
@@ -18,8 +18,8 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// Requires gRPC-Go v1.62.0 or later.
const _ = grpc.SupportPackageIsVersion8
const (
AgentService_Algo_FullMethodName = "/agent.AgentService/Algo"
@@ -32,8 +32,8 @@ const (
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AgentServiceClient interface {
Algo(ctx context.Context, in *AlgoRequest, opts ...grpc.CallOption) (*AlgoResponse, error)
Data(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*DataResponse, error)
Algo(ctx context.Context, opts ...grpc.CallOption) (AgentService_AlgoClient, error)
Data(ctx context.Context, opts ...grpc.CallOption) (AgentService_DataClient, error)
Result(ctx context.Context, in *ResultRequest, opts ...grpc.CallOption) (*ResultResponse, error)
Attestation(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (*AttestationResponse, error)
}
@@ -46,27 +46,80 @@ func NewAgentServiceClient(cc grpc.ClientConnInterface) AgentServiceClient {
return &agentServiceClient{cc}
}
func (c *agentServiceClient) Algo(ctx context.Context, in *AlgoRequest, opts ...grpc.CallOption) (*AlgoResponse, error) {
out := new(AlgoResponse)
err := c.cc.Invoke(ctx, AgentService_Algo_FullMethodName, in, out, opts...)
func (c *agentServiceClient) Algo(ctx context.Context, opts ...grpc.CallOption) (AgentService_AlgoClient, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &AgentService_ServiceDesc.Streams[0], AgentService_Algo_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
return out, nil
x := &agentServiceAlgoClient{ClientStream: stream}
return x, nil
}
func (c *agentServiceClient) Data(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*DataResponse, error) {
out := new(DataResponse)
err := c.cc.Invoke(ctx, AgentService_Data_FullMethodName, in, out, opts...)
type AgentService_AlgoClient interface {
Send(*AlgoRequest) error
CloseAndRecv() (*AlgoResponse, error)
grpc.ClientStream
}
type agentServiceAlgoClient struct {
grpc.ClientStream
}
func (x *agentServiceAlgoClient) Send(m *AlgoRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *agentServiceAlgoClient) CloseAndRecv() (*AlgoResponse, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(AlgoResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *agentServiceClient) Data(ctx context.Context, opts ...grpc.CallOption) (AgentService_DataClient, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &AgentService_ServiceDesc.Streams[1], AgentService_Data_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
return out, nil
x := &agentServiceDataClient{ClientStream: stream}
return x, nil
}
type AgentService_DataClient interface {
Send(*DataRequest) error
CloseAndRecv() (*DataResponse, error)
grpc.ClientStream
}
type agentServiceDataClient struct {
grpc.ClientStream
}
func (x *agentServiceDataClient) Send(m *DataRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *agentServiceDataClient) CloseAndRecv() (*DataResponse, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(DataResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *agentServiceClient) Result(ctx context.Context, in *ResultRequest, opts ...grpc.CallOption) (*ResultResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ResultResponse)
err := c.cc.Invoke(ctx, AgentService_Result_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, AgentService_Result_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -74,8 +127,9 @@ func (c *agentServiceClient) Result(ctx context.Context, in *ResultRequest, opts
}
func (c *agentServiceClient) Attestation(ctx context.Context, in *AttestationRequest, opts ...grpc.CallOption) (*AttestationResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AttestationResponse)
err := c.cc.Invoke(ctx, AgentService_Attestation_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, AgentService_Attestation_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -86,8 +140,8 @@ func (c *agentServiceClient) Attestation(ctx context.Context, in *AttestationReq
// All implementations must embed UnimplementedAgentServiceServer
// for forward compatibility
type AgentServiceServer interface {
Algo(context.Context, *AlgoRequest) (*AlgoResponse, error)
Data(context.Context, *DataRequest) (*DataResponse, error)
Algo(AgentService_AlgoServer) error
Data(AgentService_DataServer) error
Result(context.Context, *ResultRequest) (*ResultResponse, error)
Attestation(context.Context, *AttestationRequest) (*AttestationResponse, error)
mustEmbedUnimplementedAgentServiceServer()
@@ -97,11 +151,11 @@ type AgentServiceServer interface {
type UnimplementedAgentServiceServer struct {
}
func (UnimplementedAgentServiceServer) Algo(context.Context, *AlgoRequest) (*AlgoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Algo not implemented")
func (UnimplementedAgentServiceServer) Algo(AgentService_AlgoServer) error {
return status.Errorf(codes.Unimplemented, "method Algo not implemented")
}
func (UnimplementedAgentServiceServer) Data(context.Context, *DataRequest) (*DataResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Data not implemented")
func (UnimplementedAgentServiceServer) Data(AgentService_DataServer) error {
return status.Errorf(codes.Unimplemented, "method Data not implemented")
}
func (UnimplementedAgentServiceServer) Result(context.Context, *ResultRequest) (*ResultResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Result not implemented")
@@ -122,40 +176,56 @@ func RegisterAgentServiceServer(s grpc.ServiceRegistrar, srv AgentServiceServer)
s.RegisterService(&AgentService_ServiceDesc, srv)
}
func _AgentService_Algo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AlgoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServiceServer).Algo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AgentService_Algo_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServiceServer).Algo(ctx, req.(*AlgoRequest))
}
return interceptor(ctx, in, info, handler)
func _AgentService_Algo_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(AgentServiceServer).Algo(&agentServiceAlgoServer{ServerStream: stream})
}
func _AgentService_Data_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DataRequest)
if err := dec(in); err != nil {
type AgentService_AlgoServer interface {
SendAndClose(*AlgoResponse) error
Recv() (*AlgoRequest, error)
grpc.ServerStream
}
type agentServiceAlgoServer struct {
grpc.ServerStream
}
func (x *agentServiceAlgoServer) SendAndClose(m *AlgoResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *agentServiceAlgoServer) Recv() (*AlgoRequest, error) {
m := new(AlgoRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AgentServiceServer).Data(ctx, in)
return m, nil
}
func _AgentService_Data_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(AgentServiceServer).Data(&agentServiceDataServer{ServerStream: stream})
}
type AgentService_DataServer interface {
SendAndClose(*DataResponse) error
Recv() (*DataRequest, error)
grpc.ServerStream
}
type agentServiceDataServer struct {
grpc.ServerStream
}
func (x *agentServiceDataServer) SendAndClose(m *DataResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *agentServiceDataServer) Recv() (*DataRequest, error) {
m := new(DataRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AgentService_Data_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AgentServiceServer).Data(ctx, req.(*DataRequest))
}
return interceptor(ctx, in, info, handler)
return m, nil
}
func _AgentService_Result_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
@@ -201,14 +271,6 @@ var AgentService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "agent.AgentService",
HandlerType: (*AgentServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Algo",
Handler: _AgentService_Algo_Handler,
},
{
MethodName: "Data",
Handler: _AgentService_Data_Handler,
},
{
MethodName: "Result",
Handler: _AgentService_Result_Handler,
@@ -218,6 +280,17 @@ var AgentService_ServiceDesc = grpc.ServiceDesc{
Handler: _AgentService_Attestation_Handler,
},
},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Algo",
Handler: _AgentService_Algo_Handler,
ClientStreams: true,
},
{
StreamName: "Data",
Handler: _AgentService_Data_Handler,
ClientStreams: true,
},
},
Metadata: "agent/agent.proto",
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package algorithm
// Algorithm is an interface that specifies the API for an algorithm.
type Algorithm interface {
// Run executes the algorithm and returns the result.
Run() ([]byte, error)
}
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package binary
import (
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"github.com/ultravioletrs/cocos/agent/algorithm"
"github.com/ultravioletrs/cocos/agent/events"
"github.com/ultravioletrs/cocos/pkg/socket"
)
const socketPath = "unix_socket"
var _ algorithm.Algorithm = (*binary)(nil)
type binary struct {
algoFile string
datasets []string
logger *slog.Logger
stderr io.Writer
stdout io.Writer
}
func New(logger *slog.Logger, eventsSvc events.Service, algoFile string, datasets ...string) algorithm.Algorithm {
return &binary{
algoFile: algoFile,
datasets: datasets,
logger: logger,
stderr: &algorithm.Stderr{Logger: logger, EventSvc: eventsSvc},
stdout: &algorithm.Stdout{Logger: logger},
}
}
func (b *binary) Run() ([]byte, error) {
defer os.Remove(b.algoFile)
defer func() {
for _, file := range b.datasets {
os.Remove(file)
}
}()
listener, err := socket.StartUnixSocketServer(socketPath)
if err != nil {
return nil, fmt.Errorf("error creating stdout pipe: %v", err)
}
defer listener.Close()
// Create channels for received data and errors
dataChannel := make(chan []byte)
errorChannel := make(chan error)
var result []byte
go socket.AcceptConnection(listener, dataChannel, errorChannel)
args := append([]string{socketPath}, b.datasets...)
cmd := exec.Command(b.algoFile, args...)
cmd.Stderr = b.stderr
cmd.Stdout = b.stdout
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("error starting algorithm: %v", err)
}
if err := cmd.Wait(); err != nil {
return nil, fmt.Errorf("algorithm execution error: %v", err)
}
select {
case result = <-dataChannel:
return result, nil
case err = <-errorChannel:
return nil, fmt.Errorf("error receiving data: %v", err)
}
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package algorithm
import (
"bytes"
"io"
"log/slog"
"github.com/ultravioletrs/cocos/agent/events"
)
var (
_ io.Writer = &Stdout{}
_ io.Writer = &Stderr{}
)
const bufSize = 1024
type Stdout struct {
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
}
s.Logger.Debug(string(buf[:n]))
}
return len(p), nil
}
type Stderr struct {
Logger *slog.Logger
EventSvc events.Service
}
// 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
}
s.Logger.Error(string(buf[:n]))
}
if err := s.EventSvc.SendEvent("algorithm-run", "failed", nil); err != nil {
return len(p), err
}
return len(p), nil
}
+85
View File
@@ -0,0 +1,85 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package algorithm
import (
"strings"
"testing"
mglog "github.com/absmach/magistrala/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent/events/mocks"
)
func TestStdoutWrite(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "Single line",
input: "Hello, World!",
expected: []string{"Hello, World!"},
},
{
name: "Multiple lines",
input: "Line 1\nLine 2\nLine 3",
expected: []string{"Line 1\nLine 2\nLine 3"},
},
{
name: "Long input",
input: strings.Repeat("a", bufSize+100),
expected: []string{strings.Repeat("a", bufSize), strings.Repeat("a", 100)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stdout := &Stdout{Logger: mglog.NewMock()}
n, err := stdout.Write([]byte(tt.input))
assert.NoError(t, err)
assert.Equal(t, len(tt.input), n)
})
}
}
func TestStderrWrite(t *testing.T) {
tests := []struct {
name string
input string
expected []string
}{
{
name: "Single line",
input: "Error: Something went wrong",
expected: []string{"Error: Something went wrong"},
},
{
name: "Multiple lines",
input: "Error 1\nError 2\nError 3",
expected: []string{"Error 1\nError 2\nError 3"},
},
{
name: "Long input",
input: strings.Repeat("e", bufSize+100),
expected: []string{strings.Repeat("e", bufSize), strings.Repeat("e", 100)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockEventService := mocks.NewService(t)
mockEventService.On("SendEvent", "algorithm-run", "failed", mock.Anything).Return(nil)
stderr := &Stderr{Logger: mglog.NewMock(), EventSvc: mockEventService}
n, err := stderr.Write([]byte(tt.input))
assert.NoError(t, err)
assert.Equal(t, len(tt.input), n)
mockEventService.AssertExpectations(t)
})
}
}
-218
View File
@@ -1,218 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"fmt"
"time"
"github.com/go-kit/kit/endpoint"
kitgrpc "github.com/go-kit/kit/transport/grpc"
"github.com/ultravioletrs/cocos/agent"
"google.golang.org/grpc"
)
const svcName = "agent.AgentService"
type grpcClient struct {
algo endpoint.Endpoint
data endpoint.Endpoint
result endpoint.Endpoint
attestation endpoint.Endpoint
timeout time.Duration
}
// NewClient returns new gRPC client instance.
func NewClient(conn *grpc.ClientConn, timeout time.Duration) agent.AgentServiceClient {
return &grpcClient{
algo: kitgrpc.NewClient(
conn,
svcName,
"Algo",
encodeAlgoRequest,
decodeAlgoResponse,
agent.AlgoResponse{},
).Endpoint(),
data: kitgrpc.NewClient(
conn,
svcName,
"Data",
encodeDataRequest,
decodeDataResponse,
agent.DataResponse{},
).Endpoint(),
result: kitgrpc.NewClient(
conn,
svcName,
"Result",
encodeResultRequest,
decodeResultResponse,
agent.ResultResponse{},
).Endpoint(),
attestation: kitgrpc.NewClient(
conn,
svcName,
"Attestation",
encodeAttestationRequest,
decodeAttestationResponse,
agent.AttestationResponse{},
).Endpoint(),
timeout: timeout,
}
}
// encodeAlgoRequest is a transport/grpc.EncodeRequestFunc that
// converts a user-domain algoReq to a gRPC request.
func encodeAlgoRequest(_ context.Context, request interface{}) (interface{}, error) {
req, ok := request.(*algoReq)
if !ok {
return nil, fmt.Errorf("invalid request type: %T", request)
}
return &agent.AlgoRequest{
Algorithm: req.Algorithm,
Provider: req.Provider,
Id: req.Id,
}, nil
}
// decodeAlgoResponse is a transport/grpc.DecodeResponseFunc that
// converts a gRPC AlgoResponse to a user-domain response.
func decodeAlgoResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
_, ok := grpcResponse.(*agent.AlgoResponse)
if !ok {
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
}
return algoRes{}, nil
}
// encodeDataRequest is a transport/grpc.EncodeRequestFunc that
// converts a user-domain dataReq to a gRPC request.
func encodeDataRequest(_ context.Context, request interface{}) (interface{}, error) {
req, ok := request.(*dataReq)
if !ok {
return nil, fmt.Errorf("invalid request type: %T", request)
}
return &agent.DataRequest{
Dataset: req.Dataset,
Provider: req.Provider,
Id: req.Id,
}, nil
}
// decodeDataResponse is a transport/grpc.DecodeResponseFunc that
// converts a gRPC DataResponse to a user-domain response.
func decodeDataResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
_, ok := grpcResponse.(*agent.DataResponse)
if !ok {
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
}
return dataRes{}, nil
}
// encodeResultRequest is a transport/grpc.EncodeRequestFunc that
// converts a user-domain resultReq to a gRPC request.
func encodeResultRequest(_ context.Context, request interface{}) (interface{}, error) {
req, ok := request.(*resultReq)
if !ok {
return nil, fmt.Errorf("invalid request type: %T", request)
}
return &agent.ResultRequest{
Consumer: req.Consumer,
}, nil
}
// decodeResultResponse is a transport/grpc.DecodeResponseFunc that
// converts a gRPC ResultResponse to a user-domain response.
func decodeResultResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
response, ok := grpcResponse.(*agent.ResultResponse)
if !ok {
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
}
return resultRes{
File: response.File,
}, nil
}
// encodeAttestationRequest is a transport/grpc.EncodeRequestFunc that
// converts a user-domain attestationReq to a gRPC request.
func encodeAttestationRequest(_ context.Context, request interface{}) (interface{}, error) {
req, ok := request.(*attestationReq)
if !ok {
return nil, fmt.Errorf("invalid request type: %T", request)
}
return &agent.AttestationRequest{ReportData: req.ReportData[:]}, nil
}
// decodeAttestationResponse is a transport/grpc.DecodeResponseFunc that
// converts a gRPC AttestationResponse to a user-domain response.
func decodeAttestationResponse(_ context.Context, grpcResponse interface{}) (interface{}, error) {
response, ok := grpcResponse.(*agent.AttestationResponse)
if !ok {
return nil, fmt.Errorf("invalid response type: %T", grpcResponse)
}
return attestationRes{
File: response.File,
}, nil
}
// Algo implements the Algo method of the agent.AgentServiceClient interface.
func (c grpcClient) Algo(ctx context.Context, request *agent.AlgoRequest, _ ...grpc.CallOption) (*agent.AlgoResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
_, err := c.algo(ctx, &algoReq{Algorithm: request.Algorithm, Provider: request.Provider, Id: request.Id})
if err != nil {
return nil, err
}
return &agent.AlgoResponse{}, nil
}
// Data implements the Data method of the agent.AgentServiceClient interface.
func (c grpcClient) Data(ctx context.Context, request *agent.DataRequest, _ ...grpc.CallOption) (*agent.DataResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
_, err := c.data(ctx, &dataReq{Dataset: request.Dataset, Provider: request.Provider, Id: request.Id})
if err != nil {
return nil, err
}
return &agent.DataResponse{}, nil
}
// Result implements the Result method of the agent.AgentServiceClient interface.
func (c grpcClient) Result(ctx context.Context, request *agent.ResultRequest, _ ...grpc.CallOption) (*agent.ResultResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
res, err := c.result(ctx, &resultReq{Consumer: request.Consumer})
if err != nil {
return nil, err
}
resultRes := res.(resultRes)
return &agent.ResultResponse{File: resultRes.File}, nil
}
// Result implements the Result method of the agent.AgentServiceClient interface.
func (c grpcClient) Attestation(ctx context.Context, request *agent.AttestationRequest, _ ...grpc.CallOption) (*agent.AttestationResponse, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
res, err := c.attestation(ctx, &attestationReq{ReportData: [agent.ReportDataSize]byte(request.ReportData)})
if err != nil {
return nil, err
}
attestationRes := res.(attestationRes)
return &agent.AttestationResponse{File: attestationRes.File}, nil
}
+3 -3
View File
@@ -17,7 +17,7 @@ func algoEndpoint(svc agent.Service) endpoint.Endpoint {
return algoRes{}, err
}
algo := agent.Algorithm{Algorithm: req.Algorithm, Provider: req.Provider, ID: req.Id}
algo := agent.Algorithm{Algorithm: req.Algorithm}
err := svc.Algo(ctx, algo)
if err != nil {
@@ -36,7 +36,7 @@ func dataEndpoint(svc agent.Service) endpoint.Endpoint {
return dataRes{}, err
}
dataset := agent.Dataset{Dataset: req.Dataset, Provider: req.Provider, ID: req.Id}
dataset := agent.Dataset{Dataset: req.Dataset}
err := svc.Data(ctx, dataset)
if err != nil {
@@ -54,7 +54,7 @@ func resultEndpoint(svc agent.Service) endpoint.Endpoint {
if err := req.validate(); err != nil {
return resultRes{}, err
}
file, err := svc.Result(ctx, req.Consumer)
file, err := svc.Result(ctx)
if err != nil {
return resultRes{}, err
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/auth"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type authInterceptor struct {
auth auth.Authenticator
}
type wrappedServerStream struct {
grpc.ServerStream
ctx context.Context
}
func (s *wrappedServerStream) Context() context.Context {
return s.ctx
}
func NewAuthInterceptor(authSvc auth.Authenticator) (grpc.UnaryServerInterceptor, grpc.StreamServerInterceptor) {
ai := &authInterceptor{auth: authSvc}
return ai.AuthUnaryInterceptor(), ai.AuthStreamInterceptor()
}
func (s *authInterceptor) AuthStreamInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
switch info.FullMethod {
case agent.AgentService_Algo_FullMethodName:
if _, err := s.auth.AuthenticateUser(stream.Context(), auth.AlgorithmProviderRole); err != nil {
return status.Errorf(codes.Unauthenticated, err.Error())
}
return handler(srv, stream)
case agent.AgentService_Data_FullMethodName:
ctx, err := s.auth.AuthenticateUser(stream.Context(), auth.DataProviderRole)
if err != nil {
return status.Errorf(codes.Unauthenticated, err.Error())
}
wrapped := &wrappedServerStream{ServerStream: stream, ctx: ctx}
return handler(srv, wrapped)
default:
return handler(srv, stream)
}
}
}
func (s *authInterceptor) AuthUnaryInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
switch info.FullMethod {
case agent.AgentService_Result_FullMethodName:
ctx, err := s.auth.AuthenticateUser(ctx, auth.ConsumerRole)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, err.Error())
}
return handler(ctx, req)
default:
return handler(ctx, req)
}
}
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"testing"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/agent/mocks"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func TestAuthUnaryInterceptor(t *testing.T) {
authmock := new(mocks.Authenticator)
tests := []struct {
name string
authorized bool
method string
role auth.UserRole
wantErr bool
}{
{
name: "authorized result method",
authorized: true,
method: agent.AgentService_Result_FullMethodName,
role: auth.ConsumerRole,
wantErr: false,
},
{
name: "unauthorized result method",
authorized: false,
method: agent.AgentService_Result_FullMethodName,
role: auth.ConsumerRole,
wantErr: true,
},
{
name: "other method",
authorized: false,
method: "OtherMethod",
role: auth.ConsumerRole,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
switch tt.authorized {
case true:
mockCall := authmock.On("AuthenticateUser", context.Background(), tt.role).Return(context.Background(), nil)
mockCall.Once()
case false:
mockCall := authmock.On("AuthenticateUser", context.Background(), tt.role).Return(context.Background(), auth.ErrMissingMetadata)
mockCall.Once()
}
unaryInt, _ := NewAuthInterceptor(authmock)
_, err := unaryInt(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: tt.method}, func(ctx context.Context, req interface{}) (interface{}, error) {
return nil, nil
})
if tt.wantErr && err == nil {
t.Errorf("expected error, got none")
} else if !tt.wantErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestAuthStreamInterceptor(t *testing.T) {
authmock := new(mocks.Authenticator)
tests := []struct {
name string
authorized bool
method string
role auth.UserRole
wantErr bool
}{
{
name: "authorized algo method",
authorized: true,
method: agent.AgentService_Algo_FullMethodName,
role: auth.AlgorithmProviderRole,
wantErr: false,
},
{
name: "unauthorized algo method",
authorized: false,
method: agent.AgentService_Algo_FullMethodName,
role: auth.AlgorithmProviderRole,
wantErr: true,
},
{
name: "authorized data method",
authorized: true,
method: agent.AgentService_Data_FullMethodName,
role: auth.DataProviderRole,
wantErr: false,
},
{
name: "unauthorized data method",
authorized: false,
method: agent.AgentService_Data_FullMethodName,
role: auth.DataProviderRole,
wantErr: true,
},
{
name: "other method",
authorized: false,
method: "OtherMethod",
role: auth.DataProviderRole,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
switch tt.authorized {
case true:
mockCall := authmock.On("AuthenticateUser", mock.Anything, tt.role).Return(context.Background(), nil)
mockCall.Once()
case false:
mockCall := authmock.On("AuthenticateUser", mock.Anything, tt.role).Return(context.Background(), auth.ErrMissingMetadata)
mockCall.Once()
}
_, streamInt := NewAuthInterceptor(authmock)
err := streamInt(nil, &mockServerStream{ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs())}, &grpc.StreamServerInfo{FullMethod: tt.method}, func(srv interface{}, stream grpc.ServerStream) error {
return nil
})
if tt.wantErr && err == nil {
t.Errorf("expected error, got none")
} else if !tt.wantErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
type mockServerStream struct {
grpc.ServerStream
ctx context.Context
}
func (m *mockServerStream) Context() context.Context {
return m.ctx
}
+2 -20
View File
@@ -8,45 +8,27 @@ import (
type algoReq struct {
Algorithm []byte `protobuf:"bytes,1,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
}
func (req algoReq) validate() error {
if len(req.Algorithm) == 0 {
return errors.New("algorithm binary is required")
}
if req.Id == "" {
return errors.New("malformed entity")
}
if req.Provider == "" {
return errors.New("malformed entity")
}
return nil
}
type dataReq struct {
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"`
Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"`
Dataset []byte `protobuf:"bytes,1,opt,name=dataset,proto3" json:"dataset,omitempty"`
}
func (req dataReq) validate() error {
if len(req.Dataset) == 0 {
return errors.New("dataset CSV file is required")
}
if req.Id == "" {
return errors.New("malformed entity")
}
if req.Provider == "" {
return errors.New("malformed entity")
}
return nil
}
type resultReq struct {
Consumer string `protobuf:"bytes,1,opt,name=consumer,proto3" json:"consumer,omitempty"`
}
type resultReq struct{}
func (req resultReq) validate() error {
// No request parameters to validate, so no validation logic needed
+41 -17
View File
@@ -5,11 +5,16 @@ package grpc
import (
"context"
"errors"
"io"
"github.com/go-kit/kit/transport/grpc"
"github.com/ultravioletrs/cocos/agent"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var _ agent.AgentServiceServer = (*grpcServer)(nil)
type grpcServer struct {
algo grpc.Handler
data grpc.Handler
@@ -49,8 +54,6 @@ func decodeAlgoRequest(_ context.Context, grpcReq interface{}) (interface{}, err
return algoReq{
Algorithm: req.Algorithm,
Provider: req.Provider,
Id: req.Id,
}, nil
}
@@ -62,9 +65,7 @@ func decodeDataRequest(_ context.Context, grpcReq interface{}) (interface{}, err
req := grpcReq.(*agent.DataRequest)
return dataReq{
Dataset: req.Dataset,
Provider: req.Provider,
Id: req.Id,
Dataset: req.Dataset,
}, nil
}
@@ -73,8 +74,7 @@ func encodeDataResponse(_ context.Context, response interface{}) (interface{}, e
}
func decodeResultRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*agent.ResultRequest)
return resultReq{Consumer: req.Consumer}, nil
return resultReq{}, nil
}
func encodeResultResponse(_ context.Context, response interface{}) (interface{}, error) {
@@ -99,22 +99,46 @@ func encodeAttestationResponse(_ context.Context, response interface{}) (interfa
}, nil
}
func (s *grpcServer) Algo(ctx context.Context, req *agent.AlgoRequest) (*agent.AlgoResponse, error) {
_, res, err := s.algo.ServeGRPC(ctx, req)
// Algo implements agent.AgentServiceServer.
func (s *grpcServer) Algo(stream agent.AgentService_AlgoServer) error {
var algoFile []byte
for {
algoChunk, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return status.Error(codes.Internal, err.Error())
}
algoFile = append(algoFile, algoChunk.Algorithm...)
}
_, res, err := s.algo.ServeGRPC(stream.Context(), &agent.AlgoRequest{Algorithm: algoFile})
if err != nil {
return nil, err
return err
}
ar := res.(*agent.AlgoResponse)
return ar, nil
return stream.SendAndClose(ar)
}
func (s *grpcServer) Data(ctx context.Context, req *agent.DataRequest) (*agent.DataResponse, error) {
_, res, err := s.data.ServeGRPC(ctx, req)
if err != nil {
return nil, err
// Data implements agent.AgentServiceServer.
func (s *grpcServer) Data(stream agent.AgentService_DataServer) error {
var dataFile []byte
for {
dataChunk, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return status.Error(codes.Internal, err.Error())
}
dataFile = append(dataFile, dataChunk.Dataset...)
}
dr := res.(*agent.DataResponse)
return dr, nil
_, res, err := s.data.ServeGRPC(stream.Context(), &agent.DataRequest{Dataset: dataFile})
if err != nil {
return err
}
ar := res.(*agent.DataResponse)
return stream.SendAndClose(ar)
}
func (s *grpcServer) Result(ctx context.Context, req *agent.ResultRequest) (*agent.ResultResponse, error) {
+2 -2
View File
@@ -53,7 +53,7 @@ func (lm *loggingMiddleware) Data(ctx context.Context, dataset agent.Dataset) (e
return lm.svc.Data(ctx, dataset)
}
func (lm *loggingMiddleware) Result(ctx context.Context, consumer string) (response []byte, err error) {
func (lm *loggingMiddleware) Result(ctx context.Context) (response []byte, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method Result took %s to complete", time.Since(begin))
if err != nil {
@@ -63,7 +63,7 @@ func (lm *loggingMiddleware) Result(ctx context.Context, consumer string) (respo
lm.logger.Info(fmt.Sprintf("%s without errors", message))
}(time.Now())
return lm.svc.Result(ctx, consumer)
return lm.svc.Result(ctx)
}
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) (response []byte, err error) {
+2 -2
View File
@@ -50,13 +50,13 @@ func (ms *metricsMiddleware) Data(ctx context.Context, dataset agent.Dataset) er
return ms.svc.Data(ctx, dataset)
}
func (ms *metricsMiddleware) Result(ctx context.Context, consumer string) ([]byte, error) {
func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
defer func(begin time.Time) {
ms.counter.With("method", "result").Add(1)
ms.latency.With("method", "result").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Result(ctx, consumer)
return ms.svc.Result(ctx)
}
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) ([]byte, error) {
+174
View File
@@ -0,0 +1,174 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package auth
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"github.com/absmach/magistrala/pkg/errors"
"github.com/ultravioletrs/cocos/agent"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type UserRole string
const (
UserMetadataKey = "user-id"
SignatureMetadataKey = "signature"
ConsumerRole UserRole = "consumer"
DataProviderRole UserRole = "data-provider"
AlgorithmProviderRole UserRole = "algorithm-provider"
)
var (
ErrMissingMetadata = errors.New("missing metadata")
ErrInvalidMetadata = errors.New("invalid metadata")
ErrSignatureVerificationFailed = errors.New("signature verification failed")
)
//go:generate mockery --name Authenticator --output=../mocks --filename auth.go --quiet --note "Copyright (c) Ultraviolet \n // SPDX-License-Identifier: Apache-2.0"
type Authenticator interface {
AuthenticateUser(ctx context.Context, role UserRole) (context.Context, error)
}
type service struct {
resultConsumers []interface{}
datasetProviders []interface{}
algorithmProvider interface{}
}
func New(manifest agent.Computation) (Authenticator, error) {
s := &service{}
for _, rc := range manifest.ResultConsumers {
pubKey, err := x509.ParsePKIXPublicKey(rc.UserKey)
if err != nil {
return nil, err
}
pKey, err := decodePublicKey(pubKey)
if err != nil {
return nil, err
}
s.resultConsumers = append(s.resultConsumers, pKey)
}
for _, dp := range manifest.Datasets {
pubKey, err := x509.ParsePKIXPublicKey(dp.UserKey)
if err != nil {
return nil, err
}
pKey, err := decodePublicKey(pubKey)
if err != nil {
return nil, err
}
s.datasetProviders = append(s.datasetProviders, pKey)
}
pubKey, err := x509.ParsePKIXPublicKey(manifest.Algorithm.UserKey)
if err != nil {
return nil, err
}
pKey, err := decodePublicKey(pubKey)
if err != nil {
return nil, err
}
s.algorithmProvider = pKey
return s, nil
}
func extractSignature(md metadata.MD) (string, error) {
signature := md.Get(SignatureMetadataKey)
if len(signature) != 1 {
return "", status.Errorf(codes.Unauthenticated, "invalid metadata")
}
return signature[0], nil
}
func verifySignature(role UserRole, signature string, publicKey any) error {
hash := sha256.Sum256([]byte(role))
sigByte, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return err
}
var ok bool
switch publicKey := publicKey.(type) {
case *rsa.PublicKey:
if err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], sigByte); err != nil {
return err
}
return nil
case *ecdsa.PublicKey:
ok = ecdsa.VerifyASN1(publicKey, hash[:], sigByte)
case ed25519.PublicKey:
ok = ed25519.Verify(publicKey, []byte(role), sigByte)
}
if !ok {
return ErrSignatureVerificationFailed
}
return nil
}
func (s *service) AuthenticateUser(ctx context.Context, role UserRole) (context.Context, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, ErrMissingMetadata
}
signature, err := extractSignature(md)
if err != nil {
return nil, errors.Wrap(err, ErrInvalidMetadata)
}
switch role {
case ConsumerRole:
for i, rc := range s.resultConsumers {
if err := verifySignature(role, signature, rc); err == nil {
return agent.IndexToContext(ctx, i), nil
}
}
case DataProviderRole:
for i, dp := range s.datasetProviders {
if err := verifySignature(role, signature, dp); err == nil {
return agent.IndexToContext(ctx, i), nil
}
}
case AlgorithmProviderRole:
if err := verifySignature(role, signature, s.algorithmProvider); err == nil {
return ctx, nil
}
}
return ctx, ErrSignatureVerificationFailed
}
func decodePublicKey(key any) (pubKey any, err error) {
switch key := key.(type) {
case *rsa.PublicKey:
return key, nil
case *ecdsa.PublicKey:
return key, nil
case ed25519.PublicKey:
return key, nil
default:
return nil, errors.New("unsupported public key type")
}
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package auth
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"testing"
"github.com/absmach/magistrala/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ultravioletrs/cocos/agent"
"google.golang.org/grpc/metadata"
)
func TestAuthenticateUser(t *testing.T) {
resultConsumerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
dataProviderKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
pubEd25519Key, algorithmProviderKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
resultConsumerPubKey, err := x509.MarshalPKIXPublicKey(&resultConsumerKey.PublicKey)
require.NoError(t, err)
dataProviderPubKey, err := x509.MarshalPKIXPublicKey(&dataProviderKey.PublicKey)
require.NoError(t, err)
algorithmProviderPubKey, err := x509.MarshalPKIXPublicKey(pubEd25519Key)
require.NoError(t, err)
manifest := agent.Computation{
ResultConsumers: []agent.ResultConsumer{{UserKey: resultConsumerPubKey}},
Datasets: []agent.Dataset{{UserKey: dataProviderPubKey}},
Algorithm: agent.Algorithm{UserKey: algorithmProviderPubKey},
}
auth, err := New(manifest)
if err != nil {
t.Fatalf("failed to create authenticator: %v", err)
}
testCases := []struct {
name string
role UserRole
key any
expectedErr error
}{
{
name: "valid result consumer",
role: ConsumerRole,
key: resultConsumerKey,
expectedErr: nil,
},
{
name: "valid data provider",
role: DataProviderRole,
key: dataProviderKey,
expectedErr: nil,
},
{
name: "valid algorithm provider",
role: AlgorithmProviderRole,
key: algorithmProviderKey,
expectedErr: nil,
},
{
name: "invalid role",
role: "invalid-role",
key: resultConsumerKey,
expectedErr: ErrSignatureVerificationFailed,
},
{
name: "invalid key",
role: ConsumerRole,
key: dataProviderKey,
expectedErr: ErrSignatureVerificationFailed,
},
{
name: "missing signature",
role: ConsumerRole,
key: resultConsumerKey,
expectedErr: ErrInvalidMetadata,
},
{
name: "missing metadata",
role: ConsumerRole,
key: resultConsumerKey,
expectedErr: ErrMissingMetadata,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
signature, err := signRole(tc.role, tc.key)
if err != nil {
t.Fatalf("failed to sign role: %v", err)
}
ctx := context.Background()
switch tc.name {
case "missing signature":
ctx = metadata.NewIncomingContext(ctx, metadata.Pairs())
case "missing metadata":
default:
ctx = metadata.NewIncomingContext(ctx, metadata.Pairs(SignatureMetadataKey, signature))
}
ctx, err = auth.AuthenticateUser(ctx, tc.role)
assert.True(t, errors.Contains(err, tc.expectedErr), "expected error %v, got %v", tc.expectedErr, err)
if err == nil {
switch id, ok := agent.IndexFromContext(ctx); {
case tc.role == ConsumerRole, tc.role == DataProviderRole:
assert.True(t, ok, "expected index in context")
assert.Equal(t, 0, id, "expected index 0 in context")
default:
assert.False(t, ok, "expected no index in context")
}
}
})
}
}
func signRole(role UserRole, key crypto.PrivateKey) (string, error) {
var signature []byte
var err error
switch k := key.(type) {
case ed25519.PrivateKey:
signature, err = k.Sign(rand.Reader, []byte(role), crypto.Hash(0))
case *rsa.PrivateKey, *ecdsa.PrivateKey:
hash := sha256.Sum256([]byte(role))
signer := key.(crypto.Signer)
signature, err = signer.Sign(rand.Reader, hash[:], crypto.SHA256)
default:
return "", errors.New("unsupported key type")
}
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
+26 -39
View File
@@ -3,15 +3,12 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"reflect"
)
var (
_ fmt.Stringer = (*Datasets)(nil)
_ fmt.Stringer = (*Algorithms)(nil)
)
var _ fmt.Stringer = (*Datasets)(nil)
type AgentConfig struct {
LogLevel string `json:"log_level"`
@@ -21,16 +18,21 @@ type AgentConfig struct {
KeyFile string `json:"server_key"`
ServerCAFile string `json:"server_ca_file"`
ClientCAFile string `json:"client_ca_file"`
AttestedTls bool `json:"attested_tls"`
}
type Computation struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Datasets Datasets `json:"datasets,omitempty"`
Algorithms Algorithms `json:"algorithms,omitempty"`
ResultConsumers []string `json:"result_consumers,omitempty"`
AgentConfig AgentConfig `json:"agent_config,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Datasets Datasets `json:"datasets,omitempty"`
Algorithm Algorithm `json:"algorithm,omitempty"`
ResultConsumers []ResultConsumer `json:"result_consumers,omitempty"`
AgentConfig AgentConfig `json:"agent_config,omitempty"`
}
type ResultConsumer struct {
UserKey []byte `json:"user_key,omitempty"`
}
func (d *Datasets) String() string {
@@ -41,19 +43,10 @@ func (d *Datasets) String() string {
return string(dat)
}
func (a *Algorithms) String() string {
dat, err := json.Marshal(a)
if err != nil {
return ""
}
return string(dat)
}
type Dataset struct {
Dataset []byte `json:"-"`
Hash [32]byte `json:"hash,omitempty"`
Provider string `json:"provider,omitempty"`
ID string `json:"id,omitempty"`
Dataset []byte `json:"-"`
Hash [32]byte `json:"hash,omitempty"`
UserKey []byte `json:"user_key,omitempty"`
}
type Datasets []Dataset
@@ -61,22 +54,16 @@ type Datasets []Dataset
type Algorithm struct {
Algorithm []byte `json:"-"`
Hash [32]byte `json:"hash,omitempty"`
Provider string `json:"provider,omitempty"`
ID string `json:"id,omitempty"`
UserKey []byte `json:"user_key,omitempty"`
}
type Algorithms []Algorithm
type ManifestIndexKey struct{}
func containsID(slice interface{}, id string) int {
rangeOnMe := reflect.ValueOf(slice)
for i := 0; i < rangeOnMe.Len(); i++ {
s := rangeOnMe.Index(i)
f := s.FieldByName("ID")
if f.IsValid() {
if f.Interface() == id {
return i
}
}
}
return -1
func IndexToContext(ctx context.Context, index int) context.Context {
return context.WithValue(ctx, ManifestIndexKey{}, index)
}
func IndexFromContext(ctx context.Context) (int, bool) {
index, ok := ctx.Value(ManifestIndexKey{}).(int)
return index, ok
}
+1
View File
@@ -27,6 +27,7 @@ type AgentEvent struct {
Status string `json:"status,omitempty"`
}
//go:generate mockery --name Service --output=./mocks --filename events.go --quiet --note "Copyright (c) Ultraviolet \n // SPDX-License-Identifier: Apache-2.0"
type Service interface {
SendEvent(event, status string, details json.RawMessage) error
Close() error
+67
View File
@@ -0,0 +1,67 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
json "encoding/json"
mock "github.com/stretchr/testify/mock"
)
// Service is an autogenerated mock type for the Service type
type Service struct {
mock.Mock
}
// Close provides a mock function with given fields:
func (_m *Service) Close() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// SendEvent provides a mock function with given fields: event, status, details
func (_m *Service) SendEvent(event string, status string, details json.RawMessage) error {
ret := _m.Called(event, status, details)
if len(ret) == 0 {
panic("no return value specified for SendEvent")
}
var r0 error
if rf, ok := ret.Get(0).(func(string, string, json.RawMessage) error); ok {
r0 = rf(event, status, details)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewService(t interface {
mock.TestingT
Cleanup(func())
}) *Service {
mock := &Service{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+129
View File
@@ -0,0 +1,129 @@
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
context "context"
agent "github.com/ultravioletrs/cocos/agent"
mock "github.com/stretchr/testify/mock"
)
// Service is an autogenerated mock type for the Service type
type Service struct {
mock.Mock
}
// Algo provides a mock function with given fields: ctx, algorithm
func (_m *Service) Algo(ctx context.Context, algorithm agent.Algorithm) error {
ret := _m.Called(ctx, algorithm)
if len(ret) == 0 {
panic("no return value specified for Algo")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, agent.Algorithm) error); ok {
r0 = rf(ctx, algorithm)
} else {
r0 = ret.Error(0)
}
return r0
}
// 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)
if len(ret) == 0 {
panic("no return value specified for Attestation")
}
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) []byte); ok {
r0 = rf(ctx, reportData)
} 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)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Data provides a mock function with given fields: ctx, dataset
func (_m *Service) Data(ctx context.Context, dataset agent.Dataset) error {
ret := _m.Called(ctx, dataset)
if len(ret) == 0 {
panic("no return value specified for Data")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, agent.Dataset) error); ok {
r0 = rf(ctx, dataset)
} else {
r0 = ret.Error(0)
}
return r0
}
// Result provides a mock function with given fields: ctx
func (_m *Service) Result(ctx context.Context) ([]byte, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for Result")
}
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]byte, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []byte); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewService(t interface {
mock.TestingT
Cleanup(func())
}) *Service {
mock := &Service{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+63
View File
@@ -0,0 +1,63 @@
// Code generated by mockery v2.42.3. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
context "context"
auth "github.com/ultravioletrs/cocos/agent/auth"
mock "github.com/stretchr/testify/mock"
)
// Authenticator is an autogenerated mock type for the Authenticator type
type Authenticator struct {
mock.Mock
}
// AuthenticateUser provides a mock function with given fields: ctx, role
func (_m *Authenticator) AuthenticateUser(ctx context.Context, role auth.UserRole) (context.Context, error) {
ret := _m.Called(ctx, role)
if len(ret) == 0 {
panic("no return value specified for AuthenticateUser")
}
var r0 context.Context
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, auth.UserRole) (context.Context, error)); ok {
return rf(ctx, role)
}
if rf, ok := ret.Get(0).(func(context.Context, auth.UserRole) context.Context); ok {
r0 = rf(ctx, role)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(context.Context)
}
}
if rf, ok := ret.Get(1).(func(context.Context, auth.UserRole) error); ok {
r1 = rf(ctx, role)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewAuthenticator creates a new instance of Authenticator. 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 NewAuthenticator(t interface {
mock.TestingT
Cleanup(func())
}) *Authenticator {
mock := &Authenticator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+81 -105
View File
@@ -9,19 +9,22 @@ import (
"errors"
"fmt"
"log/slog"
"os/exec"
"os"
"slices"
"github.com/google/go-sev-guest/client"
"github.com/ultravioletrs/cocos/agent/algorithm/binary"
"github.com/ultravioletrs/cocos/agent/events"
"github.com/ultravioletrs/cocos/pkg/socket"
"golang.org/x/crypto/sha3"
)
var _ Service = (*agentService)(nil)
// ReportDataSize is the size of the report data expected by the attestation service.
const ReportDataSize = 64
const (
// ReportDataSize is the size of the report data expected by the attestation service.
ReportDataSize = 64
algoFilePermission = 0o700
)
var (
// ErrMalformedEntity indicates malformed entity specification (e.g.
@@ -31,47 +34,40 @@ var (
// when accessing a protected resource.
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
// errUndeclaredAlgorithm indicates algorithm was not declared in computation manifest.
errUndeclaredAlgorithm = errors.New("algorithm not declared in computation manifest")
// errUndeclaredAlgorithm indicates algorithm was not declared in computation manifest.
errUndeclaredDataset = errors.New("dataset not declared in computation manifest")
// errProviderMissmatch algorithm/dataset provider does not match computation manifest.
errProviderMissmatch = errors.New("provider does not match declaration on manifest")
ErrUndeclaredDataset = errors.New("dataset not declared in computation manifest")
// errAllManifestItemsReceived indicates no new computation manifest items expected.
errAllManifestItemsReceived = errors.New("all expected manifest Items have been received")
ErrAllManifestItemsReceived = errors.New("all expected manifest Items have been received")
// errUndeclaredConsumer indicates the consumer requesting results in not declared in computation manifest.
errUndeclaredConsumer = errors.New("result consumer is undeclared in computation manifest")
ErrUndeclaredConsumer = errors.New("result consumer is undeclared in computation manifest")
// errResultsNotReady indicates the computation results are not ready.
errResultsNotReady = errors.New("computation results are not yet ready")
ErrResultsNotReady = errors.New("computation results are not yet ready")
// errStateNotReady agent received a request in the wrong state.
errStateNotReady = errors.New("agent not expecting this operation in the current state")
ErrStateNotReady = errors.New("agent not expecting this operation in the current state")
// errHashMismatch provided algorithm/dataset does not match hash in manifest.
errHashMismatch = errors.New("malformed data, hash does not match manifest")
ErrHashMismatch = errors.New("malformed data, hash does not match manifest")
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
//
//go:generate mockery --name Service --output=mocks --filename agent.go --quiet --note "Copyright (c) Ultraviolet \n // SPDX-License-Identifier: Apache-2.0"
type Service interface {
Algo(ctx context.Context, algorithm Algorithm) error
Data(ctx context.Context, dataset Dataset) error
Result(ctx context.Context, consumer string) ([]byte, error)
Result(ctx context.Context) ([]byte, error)
Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error)
}
type agentService struct {
computation Computation // Holds the current computation request details.
algorithms [][]byte // Stores the algorithms received for the computation.
datasets [][]byte // Stores the datasets received for the computation.
algorithm string // Filepath to the algorithm received for the computation.
datasets []string // Filepath to the datasets received for the computation.
result []byte // Stores the result of the computation.
sm *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.
}
const (
socketPath = "unix_socket"
pyRuntime = "python3"
)
var _ Service = (*agentService)(nil)
// New instantiates the agent service implementation.
@@ -80,49 +76,57 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, cmp
sm: NewStateMachine(logger),
eventSvc: eventSvc,
}
go svc.sm.Start(ctx)
svc.sm.SendEvent(start)
svc.sm.StateFunctions[idle] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[receivingManifests] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[receivingAlgorithms] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[receivingManifest] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[receivingAlgorithm] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[receivingData] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[resultsReady] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[complete] = svc.publishEvent("in-progress", json.RawMessage{})
svc.sm.StateFunctions[running] = svc.runComputation
svc.computation = cmp
svc.sm.SendEvent(manifestsReceived)
svc.sm.SendEvent(manifestReceived)
return svc
}
func (as *agentService) Algo(ctx context.Context, algorithm Algorithm) error {
if as.sm.GetState() != receivingAlgorithms {
return errStateNotReady
if as.sm.GetState() != receivingAlgorithm {
return ErrStateNotReady
}
if len(as.computation.Algorithms) == 0 {
return errAllManifestItemsReceived
if as.algorithm != "" {
return ErrAllManifestItemsReceived
}
hash := sha3.Sum256(algorithm.Algorithm)
index := containsID(as.computation.Algorithms, algorithm.ID)
switch index {
case -1:
return errUndeclaredAlgorithm
default:
if as.computation.Algorithms[index].Provider != algorithm.Provider {
return errProviderMissmatch
}
if hash != as.computation.Algorithms[index].Hash {
return errHashMismatch
}
as.computation.Algorithms = slices.Delete(as.computation.Algorithms, index, index+1)
if hash != as.computation.Algorithm.Hash {
return ErrHashMismatch
}
as.algorithms = append(as.algorithms, algorithm.Algorithm)
f, err := os.CreateTemp("", "algorithm")
if err != nil {
return fmt.Errorf("error creating algorithm file: %v", err)
}
if len(as.computation.Algorithms) == 0 {
as.sm.SendEvent(algorithmsReceived)
if _, err := f.Write(algorithm.Algorithm); err != nil {
return fmt.Errorf("error writing algorithm to file: %v", err)
}
if err := os.Chmod(f.Name(), algoFilePermission); err != nil {
return fmt.Errorf("error changing file permissions: %v", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("error closing file: %v", err)
}
as.algorithm = f.Name()
if as.algorithm != "" {
as.sm.SendEvent(algorithmReceived)
}
return nil
@@ -130,29 +134,37 @@ func (as *agentService) Algo(ctx context.Context, algorithm Algorithm) error {
func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
if as.sm.GetState() != receivingData {
return errStateNotReady
return ErrStateNotReady
}
if len(as.computation.Datasets) == 0 {
return errAllManifestItemsReceived
return ErrAllManifestItemsReceived
}
hash := sha3.Sum256(dataset.Dataset)
index := containsID(as.computation.Datasets, dataset.ID)
switch index {
case -1:
return errUndeclaredDataset
default:
if as.computation.Datasets[index].Provider != dataset.Provider {
return errProviderMissmatch
}
if hash != as.computation.Datasets[index].Hash {
return errHashMismatch
}
as.computation.Datasets = slices.Delete(as.computation.Datasets, index, index+1)
index, ok := IndexFromContext(ctx)
if !ok {
return ErrUndeclaredDataset
}
as.datasets = append(as.datasets, dataset.Dataset)
if hash != as.computation.Datasets[index].Hash {
return ErrHashMismatch
}
as.computation.Datasets = slices.Delete(as.computation.Datasets, index, index+1)
f, err := os.CreateTemp("", fmt.Sprintf("dataset-%d", index))
if err != nil {
return fmt.Errorf("error creating dataset file: %v", err)
}
if _, err := f.Write(dataset.Dataset); err != nil {
return fmt.Errorf("error writing dataset to file: %v", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("error closing file: %v", err)
}
as.datasets = append(as.datasets, f.Name())
if len(as.computation.Datasets) == 0 {
as.sm.SendEvent(dataReceived)
@@ -161,20 +173,18 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
return nil
}
func (as *agentService) Result(ctx context.Context, consumer string) ([]byte, error) {
func (as *agentService) Result(ctx context.Context) ([]byte, error) {
if as.sm.GetState() != resultsReady {
return []byte{}, errResultsNotReady
return []byte{}, ErrResultsNotReady
}
if len(as.computation.ResultConsumers) == 0 {
return []byte{}, errAllManifestItemsReceived
return []byte{}, ErrAllManifestItemsReceived
}
index := slices.Index(as.computation.ResultConsumers, consumer)
switch index {
case -1:
return []byte{}, errUndeclaredConsumer
default:
as.computation.ResultConsumers = slices.Delete(as.computation.ResultConsumers, index, index+1)
index, ok := IndexFromContext(ctx)
if !ok {
return []byte{}, ErrUndeclaredConsumer
}
as.computation.ResultConsumers = slices.Delete(as.computation.ResultConsumers, index, index+1)
if len(as.computation.ResultConsumers) == 0 {
as.sm.SendEvent(resultsConsumed)
@@ -201,9 +211,11 @@ func (as *agentService) runComputation() {
as.sm.logger.Debug("computation run started")
defer as.sm.SendEvent(runComplete)
as.publishEvent("in-progress", json.RawMessage{})()
result, err := run(as.algorithms[0], as.datasets[0])
algorithm := binary.New(as.sm.logger, as.eventSvc, as.algorithm, as.datasets...)
result, err := algorithm.Run()
if err != nil {
as.runError = err
as.sm.logger.Warn(fmt.Sprintf("computation failed with error: %s", err.Error()))
as.publishEvent("failed", json.RawMessage{})()
return
}
@@ -218,39 +230,3 @@ func (as *agentService) publishEvent(status string, details json.RawMessage) fun
}
}
}
func run(algoContent, dataContent []byte) ([]byte, error) {
listener, err := socket.StartUnixSocketServer(socketPath)
if err != nil {
return nil, fmt.Errorf("error creating stdout pipe: %v", err)
}
defer listener.Close()
// Create channels for received data and errors
dataChannel := make(chan []byte)
errorChannel := make(chan error)
var result []byte
go socket.AcceptConnection(listener, dataChannel, errorChannel)
// Construct the Python script content with CSV data as a command-line argument
script := string(algoContent)
data := string(dataContent)
cmd := exec.Command(pyRuntime, "-c", script, data, socketPath)
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("error starting Python script: %v", err)
}
if err := cmd.Wait(); err != nil {
return nil, fmt.Errorf("python script execution error: %v", err)
}
select {
case result = <-dataChannel:
return result, nil
case err = <-errorChannel:
return nil, fmt.Errorf("error receiving data: %v", err)
}
}
+9 -9
View File
@@ -14,8 +14,8 @@ type state int
const (
idle state = iota
receivingManifests
receivingAlgorithms
receivingManifest
receivingAlgorithm
receivingData
running
resultsReady
@@ -26,8 +26,8 @@ type event int
const (
start event = iota
manifestsReceived
algorithmsReceived
manifestReceived
algorithmReceived
dataReceived
runComplete
resultsConsumed
@@ -56,13 +56,13 @@ func NewStateMachine(logger *slog.Logger) *StateMachine {
}
sm.Transitions[idle] = make(map[event]state)
sm.Transitions[idle][start] = receivingManifests
sm.Transitions[idle][start] = receivingManifest
sm.Transitions[receivingManifests] = make(map[event]state)
sm.Transitions[receivingManifests][manifestsReceived] = receivingAlgorithms
sm.Transitions[receivingManifest] = make(map[event]state)
sm.Transitions[receivingManifest][manifestReceived] = receivingAlgorithm
sm.Transitions[receivingAlgorithms] = make(map[event]state)
sm.Transitions[receivingAlgorithms][algorithmsReceived] = receivingData
sm.Transitions[receivingAlgorithm] = make(map[event]state)
sm.Transitions[receivingAlgorithm][algorithmReceived] = receivingData
sm.Transitions[receivingData] = make(map[event]state)
sm.Transitions[receivingData][dataReceived] = running
+4 -4
View File
@@ -9,17 +9,17 @@ func _() {
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[idle-0]
_ = x[receivingManifests-1]
_ = x[receivingAlgorithms-2]
_ = x[receivingManifest-1]
_ = x[receivingAlgorithm-2]
_ = x[receivingData-3]
_ = x[running-4]
_ = x[resultsReady-5]
_ = x[complete-6]
}
const _state_name = "idlereceivingManifestsreceivingAlgorithmsreceivingDatarunningresultsReadycomplete"
const _state_name = "idlereceivingManifestreceivingAlgorithmreceivingDatarunningresultsReadycomplete"
var _state_index = [...]uint8{0, 4, 22, 41, 54, 61, 73, 81}
var _state_index = [...]uint8{0, 4, 21, 39, 52, 59, 71, 79}
func (i state) String() string {
if i < 0 || i >= state(len(_state_index)-1) {
+3 -3
View File
@@ -16,9 +16,9 @@ func TestStateMachineTransitions(t *testing.T) {
event event
expected state
}{
{idle, start, receivingManifests},
{receivingManifests, manifestsReceived, receivingAlgorithms},
{receivingAlgorithms, algorithmsReceived, receivingData},
{idle, start, receivingManifest},
{receivingManifest, manifestReceived, receivingAlgorithm},
{receivingAlgorithm, algorithmReceived, receivingData},
{receivingData, dataReceived, running},
{running, runComplete, resultsReady},
{resultsReady, resultsConsumed, complete},
+4 -12
View File
@@ -1,6 +1,6 @@
# Agent CLI
This repository contains the command-line interface (CLI) tool for interacting with the Agent and manager service. The CLI allows you to perform various tasks such as running computations, uploading algorithms and datasets, and retrieving results.
This repository contains the command-line interface (CLI) tool for interacting with the Agent and manager service. The CLI allows you to perform various tasks such as running computations, uploading algorithm and datasets, and retrieving results.
## Build
@@ -12,14 +12,6 @@ make cli
## Usage
#### Run Computation
To run a computation, use the following command:
```bash
./build/cocos-cli manager run --computation '{"name": "my-computation"}'
```
#### Get attestation
Retrieves attestation information from the SEV guest and saves it to a file.
To retrieve attestation from agent, use the following command:
@@ -70,7 +62,7 @@ To validate and verify attestation from agent, use the following command:
To upload an algorithm, use the following command:
```bash
./build/cocos-cli agent algo /path/to/algorithm
./build/cocos-cli agent algo /path/to/algorithm <private_key_file_path>
```
#### Upload Dataset
@@ -78,7 +70,7 @@ To upload an algorithm, use the following command:
To upload a dataset, use the following command:
```bash
./build/cocos-cli agent data /path/to/dataset.csv
./build/cocos-cli agent data /path/to/dataset.csv <private_key_file_path>
```
#### Retrieve result
@@ -86,5 +78,5 @@ To upload a dataset, use the following command:
To retrieve the computation result, use the following command:
```bash
./build/cocos-cli agent result
./build/cocos-cli agent result <private_key_file_path>
```
+15 -7
View File
@@ -3,6 +3,7 @@
package cli
import (
"encoding/pem"
"log"
"os"
@@ -10,12 +11,12 @@ import (
"github.com/ultravioletrs/cocos/agent"
)
func (cli *CLI) NewAlgorithmsCmd() *cobra.Command {
func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
return &cobra.Command{
Use: "algo",
Short: "Upload an algorithm binary",
Example: "algo <algo_file> <id> <provider>",
Args: cobra.ExactArgs(3),
Example: "algo <algo_file> <private_key_file_path>",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
algorithmFile := args[0]
@@ -28,12 +29,19 @@ func (cli *CLI) NewAlgorithmsCmd() *cobra.Command {
algoReq := agent.Algorithm{
Algorithm: algorithm,
ID: args[1],
Provider: args[2],
}
if err := cli.agentSDK.Algo(cmd.Context(), algoReq); err != nil {
log.Fatalf("Error uploading algorithm with ID %s and provider %s: %v", algoReq.ID, algoReq.Provider, err)
privKeyFile, err := os.ReadFile(args[1])
if err != nil {
log.Fatalf("Error reading private key file: %v", err)
}
pemBlock, _ := pem.Decode(privKeyFile)
privKey := decodeKey(pemBlock)
if err := cli.agentSDK.Algo(cmd.Context(), algoReq, privKey); err != nil {
log.Fatalf("Error uploading algorithm with error: %v", err)
}
log.Println("Successfully uploaded algorithm")
+144
View File
@@ -0,0 +1,144 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/proto/check"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
type fieldType int
const (
measurementField fieldType = iota
hostDataField
)
const (
// 0o744 file permission gives RWX permission to the user and only the R permission to others.
filePermission = 0o744
// Length of the expected host data and measurement field in bytes.
hostDataLength = 32
measurementLength = 48
)
var (
errDecode = errors.New("base64 string could not be decoded")
errDataLength = errors.New("data does not have an adequate length")
errReadingBackendInfoFile = errors.New("error while reading the backend information file")
errUnmarshalJSON = errors.New("failed to unmarshal json")
errMarshalJSON = errors.New("failed to marshal json")
errWriteFile = errors.New("failed to write to file")
errBackendField = errors.New("the specified field type does not exist in the backend information")
)
type AttestationConfiguration struct {
SNPPolicy *check.Policy `json:"snp_policy,omitempty"`
RootOfTrust *check.RootOfTrust `json:"root_of_trust,omitempty"`
}
func (cli *CLI) NewBackendCmd() *cobra.Command {
return &cobra.Command{
Use: "backend [command]",
Short: "Change backend information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Change backend information\n\n")
fmt.Printf("Usage:\n %s [command]\n\n", cmd.CommandPath())
fmt.Printf("Available Commands:\n")
// Filter out "completion" command
availableCommands := make([]*cobra.Command, 0)
for _, subCmd := range cmd.Commands() {
if subCmd.Name() != "completion" {
availableCommands = append(availableCommands, subCmd)
}
}
for _, subCmd := range availableCommands {
fmt.Printf(" %-15s%s\n", subCmd.Name(), subCmd.Short)
}
fmt.Printf("\nFlags:\n")
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
fmt.Printf(" -%s, --%s %s\n", flag.Shorthand, flag.Name, flag.Usage)
})
fmt.Printf("\nUse \"%s [command] --help\" for more information about a command.\n", cmd.CommandPath())
},
}
}
func (cli *CLI) NewAddMeasurementCmd() *cobra.Command {
return &cobra.Command{
Use: "measurement",
Short: "Add measurement to the backend info file. The value should be in base64. The second parameter is backend_info.json file",
Example: "measurement <measurement> <backend_info.json>",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
if err := changeAttestationConfiguration(args[1], args[0], measurementLength, measurementField); err != nil {
log.Fatalf("Error could not change measurement data %v", err)
}
},
}
}
func (cli *CLI) NewAddHostDataCmd() *cobra.Command {
return &cobra.Command{
Use: "hostdata",
Short: "Add host data to the backend info file. The value should be in base64. The second parameter is backend_info.json file",
Example: "hostdata <host-data> <backend_info.json>",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
if err := changeAttestationConfiguration(args[1], args[0], hostDataLength, hostDataField); err != nil {
log.Fatalf("Error could not change host data %v", err)
}
},
}
}
func changeAttestationConfiguration(fileName, base64Data string, expectedLength int, field fieldType) error {
data, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
return errDecode
}
if len(data) != expectedLength {
return errDataLength
}
ac := AttestationConfiguration{}
backendInfo, err := os.ReadFile(fileName)
if err != nil {
return errors.Wrap(errReadingBackendInfoFile, err)
}
if err = json.Unmarshal(backendInfo, &ac); err != nil {
return errors.Wrap(errUnmarshalJSON, err)
}
switch field {
case measurementField:
ac.SNPPolicy.Measurement = data
case hostDataField:
ac.SNPPolicy.HostData = data
default:
return errBackendField
}
fileJson, err := json.MarshalIndent(ac, "", " ")
if err != nil {
return errors.Wrap(errMarshalJSON, err)
}
if err = os.WriteFile(fileName, fileJson, filePermission); err != nil {
return errors.Wrap(errWriteFile, err)
}
return nil
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"log"
"os"
"path"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/kds"
"github.com/google/go-sev-guest/verify/trust"
"github.com/spf13/cobra"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
)
const (
caBundleName = "ask_ark.pem"
filePermisionKeys = 0o766
)
func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
return &cobra.Command{
Use: "ca-bundle",
Short: "Fetch AMD SEV-SNPs CA Bundle (ASK and ARK)",
Example: "ca-bundle <path_to_platform_info_json>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
attestationConfiguration := grpc.AttestationConfiguration{}
err := grpc.ReadManifest(args[0], &attestationConfiguration)
if err != nil {
log.Fatalf("Error while reading manifest: %v", err)
}
product := attestationConfiguration.RootOfTrust.Product
getter := trust.DefaultHTTPSGetter()
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
bundle, err := getter.Get(caURL)
if err != nil {
log.Fatalf("Error fetching ARK and ASK from AMD KDS for product: %s, error: %v", product, err)
}
err = os.MkdirAll(path.Join(fileSavePath, product), filePermisionKeys)
if err != nil {
log.Fatalf("Error while creating directory for product name %s, error: %v", product, err)
}
bundleFilePath := path.Join(fileSavePath, product, caBundleName)
if err = saveToFile(bundleFilePath, bundle); err != nil {
log.Fatalf("Error while saving ARK-ASK to file: %v", err)
}
},
}
}
func saveToFile(fileSavePath string, content []byte) error {
file, err := os.OpenFile(fileSavePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, filePermisionKeys)
if err != nil {
return err
}
if _, err := file.Write(content); err != nil {
return err
}
return nil
}
+38 -6
View File
@@ -3,6 +3,8 @@
package cli
import (
"crypto/x509"
"encoding/pem"
"log"
"os"
@@ -14,8 +16,8 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
return &cobra.Command{
Use: "data",
Short: "Upload a dataset CSV file",
Example: "data <dataset.csv> <id> <provider>",
Args: cobra.ExactArgs(3),
Example: "data <dataset.csv> <private_key_file_path>",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
datasetFile := args[0]
@@ -27,12 +29,19 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
}
dataReq := agent.Dataset{
Dataset: dataset,
ID: args[1],
Provider: args[2],
Dataset: dataset,
}
if err := cli.agentSDK.Data(cmd.Context(), dataReq); err != nil {
privKeyFile, err := os.ReadFile(args[1])
if err != nil {
log.Fatalf("Error reading private key file: %v", err)
}
pemBlock, _ := pem.Decode(privKeyFile)
privKey := decodeKey(pemBlock)
if err := cli.agentSDK.Data(cmd.Context(), dataReq, privKey); err != nil {
log.Fatalf("Error uploading dataset: %v", err)
}
@@ -40,3 +49,26 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
},
}
}
func decodeKey(b *pem.Block) interface{} {
switch b.Type {
case rsaKeyType:
privKey, err := x509.ParsePKCS8PrivateKey(b.Bytes)
if err != nil {
privKey, err = x509.ParsePKCS1PrivateKey(b.Bytes)
if err != nil {
log.Fatalf("Error parsing private key: %v", err)
}
}
return privKey
case ecdsaKeyType:
privKey, err := x509.ParseECPrivateKey(b.Bytes)
if err != nil {
log.Fatalf("Error parsing private key: %v", err)
}
return privKey
default:
log.Fatalf("Error decoding key")
return nil
}
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"encoding/hex"
"log"
"os"
"github.com/spf13/cobra"
"golang.org/x/crypto/sha3"
)
func (cli *CLI) NewFileHashCmd() *cobra.Command {
return &cobra.Command{
Use: "file-hash",
Short: "Compute the sha3-256 hash of a file",
Example: "file-hash <file>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fileName := args[0]
file, err := os.ReadFile(fileName)
if err != nil {
log.Fatalf("Error reading dataset file: %v", err)
}
hashBytes := sha3.Sum256(file)
hash := hex.EncodeToString(hashBytes[:])
log.Println("Hash of file:", hash)
},
}
}
+128
View File
@@ -0,0 +1,128 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"log"
"os"
"reflect"
"github.com/spf13/cobra"
)
const (
keyBitSize = 4096
rsaKeyType = "PRIVATE KEY"
ecdsaKeyType = "EC PRIVATE KEY"
ed25519KeyType = "PRIVATE KEY"
publicKeyType = "PUBLIC KEY"
publicKeyFile = "public.pem"
privateKeyFile = "private.pem"
)
var KeyType string
func (cli *CLI) NewKeysCmd() *cobra.Command {
return &cobra.Command{
Use: "keys",
Short: "Generate a new public/private key pair",
Long: "Generates a new public/private key pair using an algorithm of the users choice.\n" +
"Supported algorithms are RSA, ecdsa, and ed25519.",
Example: "./build/cocos-cli keys -k rsa",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
switch KeyType {
case "ecdsa":
privEcdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatalf("Error generating keys: %v", err)
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privEcdsaKey.PublicKey)
if err != nil {
log.Fatalf("Error marshalling public key: %v", err)
}
generateAndWriteKeys(privEcdsaKey, pubKeyBytes, ecdsaKeyType)
case "ed25519":
pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Fatalf("Error generating keys: %v", err)
}
pubKey, err := x509.MarshalPKIXPublicKey(pubEd25519Key)
if err != nil {
log.Fatalf("Error marshalling public key: %v", err)
}
generateAndWriteKeys(privEd25519Key, pubKey, ed25519KeyType)
// Default to RSA
default:
privKey, err := rsa.GenerateKey(rand.Reader, keyBitSize)
if err != nil {
log.Fatalf("Error generating keys: %v", err)
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
log.Fatalf("Error marshalling public key: %v", err)
}
generateAndWriteKeys(privKey, pubKeyBytes, rsaKeyType)
}
},
}
}
func generateAndWriteKeys(privKey interface{}, pubKeyBytes []byte, keyType string) {
privFile, err := os.Create(privateKeyFile)
if err != nil {
log.Fatalf("Error creating private key file: %v", err)
}
defer privFile.Close()
var b []byte
switch privKey := privKey.(type) {
case *rsa.PrivateKey:
b = x509.MarshalPKCS1PrivateKey(privKey)
case *ecdsa.PrivateKey:
b, err = x509.MarshalECPrivateKey(privKey)
case ed25519.PrivateKey:
b, err = x509.MarshalPKCS8PrivateKey(privKey)
}
if err != nil {
log.Printf("Error marshalling private key: %v", err)
return
}
if err := pem.Encode(privFile, &pem.Block{
Type: keyType,
Bytes: b,
}); err != nil {
log.Printf("Error encoding private key: %v", err)
return
}
pubFile, err := os.Create(publicKeyFile)
if err != nil {
log.Printf("Error creating public key file: %v", err)
return
}
defer pubFile.Close()
if err := pem.Encode(pubFile, &pem.Block{
Type: publicKeyType,
Bytes: pubKeyBytes,
}); err != nil {
log.Printf("Error encoding public key: %v", err)
return
}
log.Printf("Successfully generated public/private key pair of type: %s", reflect.TypeOf(privKey).String())
}
+16 -4
View File
@@ -3,6 +3,7 @@
package cli
import (
"encoding/pem"
"log"
"os"
@@ -13,13 +14,24 @@ const resultFilePath = "result.bin"
func (cli *CLI) NewResultsCmd() *cobra.Command {
return &cobra.Command{
Use: "result",
Short: "Retrieve computation result file",
Args: cobra.ExactArgs(1),
Use: "result",
Short: "Retrieve computation result file",
Example: "result <private_key_file_path>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
log.Println("Retrieving computation result file")
result, err := cli.agentSDK.Result(cmd.Context(), args[0])
privKeyFile, err := os.ReadFile(args[0])
if err != nil {
log.Fatalf("Error reading private key file: %v", err)
}
pemBlock, _ := pem.Decode(privKeyFile)
var result []byte
privKey := decodeKey(pemBlock)
result, err = cli.agentSDK.Result(cmd.Context(), privKey)
if err != nil {
log.Fatalf("Error retrieving computation result: %v", err)
}
+3 -5
View File
@@ -2,15 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"github.com/ultravioletrs/cocos/agent"
)
import "github.com/ultravioletrs/cocos/pkg/sdk"
type CLI struct {
agentSDK agent.Service
agentSDK sdk.SDK
}
func New(agentSDK agent.Service) *CLI {
func New(agentSDK sdk.SDK) *CLI {
return &CLI{
agentSDK: agentSDK,
}
+33 -10
View File
@@ -6,19 +6,22 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"log/slog"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/mdlayher/vsock"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/api"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/agent/events"
"github.com/ultravioletrs/cocos/internal"
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/manager/qemu"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
@@ -43,6 +46,7 @@ func main() {
log.Fatal(err)
}
defer conn.Close()
var level slog.Level
if err := level.UnmarshalText([]byte(cfg.AgentConfig.LogLevel)); err != nil {
log.Println(err)
@@ -53,10 +57,11 @@ func main() {
eventSvc, err := events.New(svcName, cfg.ID, manager.ManagerVsockPort)
if err != nil {
log.Printf("failed to create events service %s", err.Error())
logger.Error(fmt.Sprintf("failed to create events service %s", err.Error()))
return
}
defer eventSvc.Close()
svc := newService(ctx, logger, eventSvc, cfg)
grpcServerConfig := server.Config{
@@ -66,13 +71,21 @@ func main() {
KeyFile: cfg.AgentConfig.KeyFile,
ServerCAFile: cfg.AgentConfig.ServerCAFile,
ClientCAFile: cfg.AgentConfig.ClientCAFile,
AttestedTLS: cfg.AgentConfig.AttestedTls,
}
registerAgentServiceServer := func(srv *grpc.Server) {
reflection.Register(srv)
agent.RegisterAgentServiceServer(srv, agentgrpc.NewServer(svc))
}
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger)
authSvc, err := auth.New(cfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to create auth service %s", err.Error()))
return
}
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger, svc, authSvc)
g.Go(func() error {
return gs.Start()
@@ -91,32 +104,42 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
svc := agent.New(ctx, logger, eventSvc, cmp)
svc = api.LoggingMiddleware(svc, logger)
counter, latency := internal.MakeMetrics(svcName, "api")
counter, latency := prometheus.MakeMetrics(svcName, "api")
svc = api.MetricsMiddleware(svc, counter, latency)
return svc
}
func readConfig() (agent.Computation, error) {
l, err := vsock.Listen(manager.VsockConfigPort, nil)
l, err := vsock.Listen(qemu.VsockConfigPort, nil)
if err != nil {
return agent.Computation{}, err
}
defer l.Close()
conn, err := l.Accept()
if err != nil {
return agent.Computation{}, err
}
defer conn.Close()
b := make([]byte, 1024)
n, err := conn.Read(b)
if err != nil {
return agent.Computation{}, err
var buffer []byte
for {
chunk := make([]byte, 1024)
n, err := conn.Read(chunk)
if err != nil {
if err == io.EOF {
break
}
return agent.Computation{}, err
}
buffer = append(buffer, chunk[:n]...)
}
ac := agent.Computation{
AgentConfig: agent.AgentConfig{},
}
if err := json.Unmarshal(b[:n], &ac); err != nil {
if err := json.Unmarshal(buffer, &ac); err != nil {
return agent.Computation{}, err
}
if ac.AgentConfig.LogLevel == "" {
+42 -8
View File
@@ -6,12 +6,13 @@ import (
"fmt"
"log"
"os"
"path"
mglog "github.com/absmach/magistrala/logger"
"github.com/caarlos0/env/v11"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/ultravioletrs/cocos/cli"
"github.com/ultravioletrs/cocos/internal/env"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
"github.com/ultravioletrs/cocos/pkg/clients/grpc/agent"
"github.com/ultravioletrs/cocos/pkg/sdk"
@@ -21,10 +22,12 @@ const (
svcName = "cli"
envPrefixAgentGRPC = "AGENT_GRPC_"
completion = "completion"
filePermision = 0o755
cocosDirectory = ".cocos"
)
type config struct {
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
}
func main() {
@@ -33,13 +36,24 @@ func main() {
log.Fatalf("failed to load %s configuration : %s", svcName, err)
}
homePath, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Error fetching user home directory: %v", err)
}
directoryCachePath := path.Join(homePath, cocosDirectory)
if err := os.MkdirAll(directoryCachePath, filePermision); err != nil {
log.Fatalf("Error while creating directory %s, error: %v", directoryCachePath, err)
}
logger, err := mglog.New(os.Stdout, cfg.LogLevel)
if err != nil {
log.Fatalf("Error creating logger: %s", err)
}
agentGRPCConfig := grpc.Config{}
if err := env.Parse(&agentGRPCConfig, env.Options{Prefix: envPrefixAgentGRPC}); err != nil {
if err := env.ParseWithOptions(&agentGRPCConfig, env.Options{Prefix: envPrefixAgentGRPC}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s gRPC client configuration : %s", svcName, err))
return
}
@@ -83,16 +97,36 @@ func main() {
},
}
keysCmd := cliSVC.NewKeysCmd()
attestationCmd := cliSVC.NewAttestationCmd()
backendCmd := cliSVC.NewBackendCmd()
// Agent Commands
rootCmd.AddCommand(cliSVC.NewAlgorithmsCmd())
rootCmd.AddCommand(cliSVC.NewAlgorithmCmd())
rootCmd.AddCommand(cliSVC.NewDatasetsCmd())
rootCmd.AddCommand(cliSVC.NewResultsCmd())
attestaionCmd := cliSVC.NewAttestationCmd()
rootCmd.AddCommand(attestaionCmd)
rootCmd.AddCommand(attestationCmd)
rootCmd.AddCommand(cliSVC.NewFileHashCmd())
rootCmd.AddCommand(backendCmd)
rootCmd.AddCommand(keysCmd)
rootCmd.AddCommand(cliSVC.NewCABundleCmd(directoryCachePath))
// Attestation commands
attestaionCmd.AddCommand(cliSVC.NewGetAttestationCmd())
attestaionCmd.AddCommand(cliSVC.NewValidateAttestationValidationCmd())
attestationCmd.AddCommand(cliSVC.NewGetAttestationCmd())
attestationCmd.AddCommand(cliSVC.NewValidateAttestationValidationCmd())
// Flags
keysCmd.PersistentFlags().StringVarP(
&cli.KeyType,
"key-type",
"k",
"rsa",
"User Key type",
)
// Backend information commands
backendCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
backendCmd.AddCommand(cliSVC.NewAddHostDataCmd())
if err := rootCmd.Execute(); err != nil {
logger.Error(fmt.Sprintf("Command execution failed: %s", err))
+19 -22
View File
@@ -8,14 +8,16 @@ import (
"fmt"
"log"
"log/slog"
"net/url"
"os"
"strings"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/jaeger"
"github.com/absmach/magistrala/pkg/prometheus"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/internal"
"github.com/ultravioletrs/cocos/internal/env"
jaegerclient "github.com/ultravioletrs/cocos/internal/jaeger"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/manager/api"
managerapi "github.com/ultravioletrs/cocos/manager/api/grpc"
@@ -35,13 +37,14 @@ const (
)
type config struct {
LogLevel string `env:"MANAGER_LOG_LEVEL" envDefault:"info"`
JaegerURL string `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"`
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
LogLevel string `env:"MANAGER_LOG_LEVEL" envDefault:"info"`
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:14268/api/traces"`
TraceRatio float64 `env:"MG_JAEGER_TRACE_RATIO" envDefault:"1.0"`
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
var cfg config
@@ -55,14 +58,13 @@ func main() {
}
if cfg.InstanceID == "" {
cfg.InstanceID, err = uuid.New().ID()
if err != nil {
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
logger.Error(fmt.Sprintf("Failed to generate instance ID: %s", err))
return
}
}
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID)
tp, err := jaeger.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
if err != nil {
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
}
@@ -74,19 +76,15 @@ func main() {
tracer := tp.Tracer(svcName)
qemuCfg := qemu.Config{}
if err := env.Parse(&qemuCfg, env.Options{Prefix: envPrefixQemu}); err != nil {
if err := env.ParseWithOptions(&qemuCfg, env.Options{Prefix: envPrefixQemu}); err != nil {
logger.Error(fmt.Sprintf("failed to load QEMU configuration: %s", err))
return
}
exe, args, err := qemu.ExecutableAndArgs(qemuCfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to parse QEMU configuration: %s", err))
return
}
logger.Info(fmt.Sprintf("%s %s", exe, strings.Join(args, " ")))
args := qemuCfg.ConstructQemuArgs()
logger.Info(strings.Join(args, " "))
managerGRPCConfig := grpc.Config{}
if err := env.Parse(&managerGRPCConfig, env.Options{Prefix: envPrefixGRPC}); err != nil {
if err := env.ParseWithOptions(&managerGRPCConfig, env.Options{Prefix: envPrefixGRPC}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s gRPC client configuration : %s", svcName, err))
return
}
@@ -110,24 +108,23 @@ func main() {
mc := managerapi.NewClient(pc, svc, eventsChan)
g.Go(func() error {
return mc.Process(ctx)
return mc.Process(ctx, cancel)
})
if err := g.Wait(); err != nil {
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
}
err = internal.DeleteFilesInDir(qemuCfg.TmpFileLoc)
if err != nil {
if err = internal.DeleteFilesInDir(qemuCfg.TmpFileLoc); err != nil {
logger.Error(err.Error())
}
}
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, eventsChan chan *pkgmanager.ClientStreamMessage) manager.Service {
svc := manager.New(qemuCfg, logger, eventsChan)
svc := manager.New(qemuCfg, logger, eventsChan, qemu.NewVM)
go svc.RetrieveAgentEventsLogs()
svc = api.LoggingMiddleware(svc, logger)
counter, latency := internal.MakeMetrics(svcName, "api")
counter, latency := prometheus.MakeMetrics(svcName, "api")
svc = api.MetricsMiddleware(svc, counter, latency)
svc = tracing.New(svc, tracer)
+42 -35
View File
@@ -1,58 +1,65 @@
module github.com/ultravioletrs/cocos
go 1.21.6
go 1.22.4
require (
github.com/absmach/magistrala v0.0.0-20240119191055-d95283d31472
github.com/caarlos0/env/v7 v7.1.0
github.com/cenkalti/backoff/v4 v4.2.1
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519
github.com/absmach/magistrala v0.14.1-0.20240709113739-04c359462746
github.com/caarlos0/env/v11 v11.1.0
github.com/cenkalti/backoff/v4 v4.3.0
github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752
github.com/go-kit/kit v0.13.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-sev-guest v0.10.1
github.com/google/go-sev-guest v0.11.1
github.com/mdlayher/vsock v1.2.1
github.com/prometheus/client_golang v1.18.0
github.com/spf13/cobra v1.8.0
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
go.opentelemetry.io/otel/trace v1.21.0
golang.org/x/crypto v0.19.0
golang.org/x/sync v0.6.0
google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.32.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0
go.opentelemetry.io/otel/trace v1.28.0
golang.org/x/crypto v0.25.0
golang.org/x/sync v0.7.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
)
require (
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
)
require (
cloud.google.com/go/compute v1.23.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
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.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-configfs-tsm v0.2.2 // indirect
github.com/google/logger v1.1.1 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.52.2 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+79 -122
View File
@@ -1,31 +1,20 @@
cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/absmach/magistrala v0.0.0-20240119191055-d95283d31472 h1:MdS3yTTJP/OSFkMJ4+cJ0LnRS9OyPtPxsR0mSFYglXw=
github.com/absmach/magistrala v0.0.0-20240119191055-d95283d31472/go.mod h1:02Ip3Ow7JlPT4nb2TdZcOuf7C3ac8tzyEVwB+H1G40g=
github.com/absmach/magistrala v0.14.1-0.20240709113739-04c359462746 h1:Tj567KeGVygjTsSCxn4++skKiz9GkPugM1KMdIFxvfw=
github.com/absmach/magistrala v0.14.1-0.20240709113739-04c359462746/go.mod h1:CIx3OsPFc4doJZmBWSA6LNWefcznKv9c3cLOxNxL4q4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/caarlos0/env/v11 v11.1.0 h1:a5qZqieE9ZfzdvbbdhTalRrHT5vu/4V1/ad1Ka6frhI=
github.com/caarlos0/env/v11 v11.1.0/go.mod h1:LwgkYk1kDvfGpHthrWWLof3Ny7PezzFwS4QrsJdHTMo=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519 h1:OpkN/n40cmKenDQS+IOAeW9DLhYy4DADSeZnouCEV/E=
github.com/digitalocean/go-libvirt v0.0.0-20221205150000-2939327a8519/go.mod h1:WyJJyfmJ0gWJvjV+ZH4DOgtOYZc1KOvYyBXWCLKxsUU=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752 h1:NI7XEcHzWVvBfVjSVK6Qk4wmrUfoyQxCNpBjrHelZFk=
github.com/digitalocean/go-libvirt v0.0.0-20240709142323-d8406205c752/go.mod h1:/Ok8PA2qi/ve0Py38+oL+VxoYmlowigYRyLEODRYdgc=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
@@ -33,33 +22,33 @@ github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBj
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98=
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-sev-guest v0.10.1 h1:kZIcGD513eqInbezqxchTtOLJlVXALyWlIXu5E0tNJE=
github.com/google/go-sev-guest v0.10.1/go.mod h1:/5hrgGWqG7+MPTXKhQz+v9ZE+Eh4MCBdItCNwgshlrk=
github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vvXN24k=
github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
@@ -68,100 +57,68 @@ github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.52.2 h1:LW8Vk7BccEdONfrJBDffQGRtpSzi5CQaRZGtboOO2ck=
github.com/prometheus/common v0.52.2/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg=
google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0=
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 h1:OPXtXn7fNMaXwO3JvOmF1QyTc00jsSFFz1vXXBOdCDo=
google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+2 -1
View File
@@ -1 +1,2 @@
source "$BR2_EXTERNAL_COCOS_PATH/package/agent/Config.in"
source "$BR2_EXTERNAL_COCOS_PATH/package/agent/Config.in"
source "$BR2_EXTERNAL_COCOS_PATH/package/wasmtime/Config.in"
+10 -3
View File
@@ -7,6 +7,7 @@ BR2_TARGET_GENERIC_ISSUE="Welcome to Cocos"
BR2_PACKAGE_DHCP=y
BR2_PACKAGE_DHCP_CLIENT=y
BR2_INIT_SYSTEMD=y
BR2_SYSTEM_BIN_SH_BASH=y
# Filesystem
# BR2_TARGET_ROOTFS_TAR is not set
@@ -43,6 +44,12 @@ BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
BR2_PACKAGE_HOST_QEMU=y
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y
# Python
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON_PIP=y
# Docker
BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS=y
BR2_PACKAGE_LIBSECCOMP=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_DOCKER_CLI=y
BR2_PACKAGE_DOCKER_COMPOSE=y
BR2_PACKAGE_DOCKER_ENGINE=y
BR2_PACKAGE_CONTAINERD=y
BR2_PACKAGE_RUNC=y
+1 -1
View File
@@ -3,6 +3,6 @@ config BR2_PACKAGE_AGENT
default y
help
Confidential Computing Agent is a state machine capable of
receiving data and algorithms, running computations, and
receiving datasets and algorithm, running computations, and
fetching the attestation report from within the
Confidential VM.
+4 -3
View File
@@ -12,14 +12,15 @@ define AGENT_BUILD_CMDS
endef
define AGENT_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0755 $(@D)/build/cocos-agent $(TARGET_DIR)/bin
$(INSTALL) -D -m 0750 $(@D)/build/cocos-agent $(TARGET_DIR)/bin
mkdir -p $(TARGET_DIR)/var/log/cocos
mkdir -p $(TARGET_DIR)/cocos/
endef
define AGENT_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 0644 $(@D)/init/systemd/cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/cocos-agent.service
$(INSTALL) -D -m 0755 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos/agent_start_script.sh
$(INSTALL) -D -m 0640 $(@D)/init/systemd/cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/cocos-agent.service
$(INSTALL) -D -m 0750 $(@D)/init/systemd/cocos_network_setup.sh $(TARGET_DIR)/cocos/cocos_network_setup.sh
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos/agent_start_script.sh
endef
$(eval $(golang-package))
+5
View File
@@ -0,0 +1,5 @@
config BR2_PACKAGE_WASMTIME
bool "wasmtime"
help
Wasmtime is a standalone runtime for WebAssembly.
https://github.com/bytecodealliance/wasmtime
+11
View File
@@ -0,0 +1,11 @@
WASMTIME_SITE = https://wasmtime.dev/install.sh
define WASMTIME_BUILD_CMDS
curl $(WASMTIME_SITE) -sSf | bash
endef
define WASMTIME_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0755 ~/.wasmtime/bin/wasmtime $(TARGET_DIR)/usr/bin/wasmtime
endef
$(eval $(generic-package))
+9 -14
View File
@@ -1,21 +1,16 @@
#!/bin/sh
# The variable ETH_IFACE contains the name the systemd gave to the network interface.
# The systemd configures the name based on the QEMU parameters.
# The parts of the name enp0s2 mean:
# et - ethernet card. It means this is the ethernet interface.
# p - means that the interface is connected to a PCI bus.
# 0 - the interface is connected to bus 0.
# s2 -the interface is connected to slot 2.
NUM_OF_PERMITED_IFACE=1
# The variable ETH_IFACE value must match the name configured through QEMU parameters for the network device.
# The bus number and slot number are configured through QEMU device parameters, parameters
# addr (for slot number), and bus (for bus number).
ETH_IFACE=enp0s2
NUM_OF_IFACE=$(ip route | grep -Eo 'dev [a-z0-9]+' | awk '{ print $2 }' | sort | uniq | wc -l)
ip link set dev $ETH_IFACE up
dhclient $ETH_IFACE
AGENT_GRPC_HOST=$(ip -4 addr show $ETH_IFACE | grep inet | awk '{print $2}' | cut -d/ -f1)
if [ $NUM_OF_IFACE -gt $NUM_OF_PERMITED_IFACE ]; then
echo "More then one network interface in the VM"
exit 1
fi
DEFAULT_IFACE=$(route | grep '^default' | grep -o '[^ ]*$')
AGENT_GRPC_HOST=$(ip -4 addr show $DEFAULT_IFACE | grep inet | awk '{print $2}' | cut -d/ -f1)
export AGENT_GRPC_HOST
+1
View File
@@ -9,6 +9,7 @@ StandardError=file:/var/log/cocos/agent.stderr
Environment=AGENT_GRPC_PORT=7002
Environment=AGENT_LOG_LEVEL=info
ExecStartPre=/cocos/cocos_network_setup.sh
ExecStart=/cocos/agent_start_script.sh
[Install]
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
# IFACES are all network interfaces excluding lo (LOOPBACK) and sit interfaces
IFACES=$(ip link show | grep -vE 'LOOPBACK|sit*' | awk -F': ' '{print $2}')
# This for loop brings up all network interfaces in IFACES and dhclient obtains an IP address for the every interface
for IFACE in $IFACES; do
STATE=$(ip link show $IFACE | grep DOWN)
if [ -n "$STATE" ]; then
ip link set $IFACE up
fi
IP_ADDR=$(ip addr show $IFACE | grep 'inet ')
if [ -z "$IP_ADDR" ]; then
dhclient $IFACE
fi
done
-6
View File
@@ -1,6 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
// Package env contains utility tools for handling
// environment-based service configuration.
package env
-38
View File
@@ -1,38 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package env
import "github.com/caarlos0/env/v7"
type Options struct {
// Environment keys and values that will be accessible for the service
Environment map[string]string
// TagName specifies another tagname to use rather than the default env
TagName string
// RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault'
RequiredIfNoDef bool
// OnSet allows to run a function when a value is set
OnSet env.OnSetFn
// Prefix define a prefix for each key
Prefix string
}
func Parse(v interface{}, opts ...Options) error {
var actOpts []env.Options
for _, opt := range opts {
actOpts = append(actOpts, env.Options{
Environment: opt.Environment,
TagName: opt.TagName,
RequiredIfNoDef: opt.RequiredIfNoDef,
OnSet: opt.OnSet,
Prefix: opt.Prefix,
})
}
return env.Parse(v, actOpts...)
}
-4
View File
@@ -1,4 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package jaeger
-80
View File
@@ -1,80 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package jaeger
import (
"context"
"errors"
"net/url"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
var (
errNoURL = errors.New("URL is empty")
errNoSvcName = errors.New("Service Name is empty")
errUnsupportedTraceURLScheme = errors.New("unsupported tracing url scheme")
)
// NewProvider initializes Jaeger TraceProvider.
func NewProvider(ctx context.Context, svcName, jaegerurl, instanceID string) (*trace.TracerProvider, error) {
if jaegerurl == "" {
return nil, errNoURL
}
if svcName == "" {
return nil, errNoSvcName
}
jurl, err := url.Parse(jaegerurl)
if err != nil {
return nil, err
}
var exporter *otlptrace.Exporter
switch jurl.Scheme {
case "http":
exporter, err = otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(jurl.Host), otlptracehttp.WithURLPath(jurl.Path), otlptracehttp.WithInsecure())
if err != nil {
return nil, err
}
case "https":
exporter, err = otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(jurl.Host), otlptracehttp.WithURLPath(jurl.Path))
if err != nil {
return nil, err
}
default:
return nil, errUnsupportedTraceURLScheme
}
attributes := []attribute.KeyValue{
semconv.ServiceNameKey.String(svcName),
attribute.String("host.id", instanceID),
}
hostAttr, err := resource.New(ctx, resource.WithHost(), resource.WithOSDescription(), resource.WithContainer())
if err != nil {
return nil, err
}
attributes = append(attributes, hostAttr.Attributes()...)
tp := trace.NewTracerProvider(
trace.WithSampler(trace.AlwaysSample()),
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
attributes...,
)),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
return tp, nil
}
-27
View File
@@ -1,27 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package internal
import (
kitprometheus "github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
// MakeMetrics returns an instance of metrics.
func MakeMetrics(namespace, subsystem string) (*kitprometheus.Counter, *kitprometheus.Summary) {
counter := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "request_count",
Help: "Number of requests received.",
}, []string{"method"})
latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: namespace,
Subsystem: subsystem,
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
Name: "request_latency_microseconds",
Help: "Total duration of requests in microseconds.",
}, []string{"method"})
return counter, latency
}
+115 -2
View File
@@ -5,36 +5,58 @@ package grpc
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log/slog"
"math/big"
"net"
"os"
"time"
"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"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
const (
stopWaitTime = 5 * time.Second
stopWaitTime = 5 * time.Second
organization = "Ultraviolet"
country = "Serbia"
province = ""
locality = "Belgrade"
streetAddress = "Bulevar Arsenija Carnojevica 103"
postalCode = "11000"
notAfterYear = 1
notAfterMonth = 0
notAfterDay = 0
)
type Server struct {
server.BaseServer
server *grpc.Server
registerService serviceRegister
agent agent.Service
authSvc auth.Authenticator
}
type serviceRegister func(srv *grpc.Server)
var _ server.Server = (*Server)(nil)
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger *slog.Logger) server.Server {
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.Config, registerService serviceRegister, logger *slog.Logger, agentSvc agent.Service, authSvc auth.Authenticator) server.Server {
listenFullAddress := fmt.Sprintf("%s:%s", config.Host, config.Port)
return &Server{
BaseServer: server.BaseServer{
@@ -46,6 +68,8 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser
Logger: logger,
},
registerService: registerService,
agent: agentSvc,
authSvc: authSvc,
}
}
@@ -55,6 +79,12 @@ func (s *Server) Start() error {
grpc.StatsHandler(otelgrpc.NewServerHandler()),
}
if s.authSvc != nil {
unary, stream := agentgrpc.NewAuthInterceptor(s.authSvc)
grpcServerOptions = append(grpcServerOptions, grpc.UnaryInterceptor(unary))
grpcServerOptions = append(grpcServerOptions, grpc.StreamInterceptor(stream))
}
listener, err := net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
@@ -62,6 +92,24 @@ func (s *Server) Start() error {
creds := grpc.Creds(insecure.NewCredentials())
switch {
case s.Config.AttestedTLS:
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.agent)
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
certificate, err := tls.X509KeyPair(certificateBytes, privateKeyBytes)
if err != nil {
return fmt.Errorf("falied due to invalid key pair: %w", err)
}
tlsConfig := &tls.Config{
ClientAuth: tls.NoClientCert,
Certificates: []tls.Certificate{certificate},
}
creds = grpc.Creds(credentials.NewTLS(tlsConfig))
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested TLS", s.Name, s.Address))
case s.Config.CertFile != "" || s.Config.KeyFile != "":
certificate, err := loadX509KeyPair(s.Config.CertFile, s.Config.KeyFile)
if err != nil {
@@ -179,3 +227,68 @@ func loadX509KeyPair(certfile, keyfile string) (tls.Certificate, error) {
}
return tls.X509KeyPair(cert, key)
}
func generateCertificatesForATLS(svc agent.Service) ([]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)
}
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal the public key: %w", err)
}
// The Attestation Report will be added as an X.509 certificate extension
attestationReport, err := svc.Attestation(context.Background(), sha3.Sum512(publicKeyBytes))
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch the attestation report: %w", err)
}
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(202403311),
Subject: pkix.Name{
Organization: []string{organization},
Country: []string{country},
Province: []string{province},
Locality: []string{locality},
StreetAddress: []string{streetAddress},
PostalCode: []string{postalCode},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(notAfterYear, notAfterMonth, notAfterDay),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6},
Critical: false,
Value: attestationReport,
},
},
}
certDERBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &privateKey.PublicKey, privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
}
certBytes := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDERBytes,
})
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal the private key: %w", err)
}
keyBytes := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyBytes,
})
return certBytes, keyBytes, nil
}
+7 -6
View File
@@ -17,12 +17,13 @@ type Server interface {
}
type Config struct {
Host string `env:"HOST" envDefault:""`
Port string `env:"PORT" envDefault:""`
CertFile string `env:"SERVER_CERT" envDefault:""`
KeyFile string `env:"SERVER_KEY" envDefault:""`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
ClientCAFile string `env:"CLIENT_CA_CERTS" envDefault:""`
Host string `env:"HOST" envDefault:""`
Port string `env:"PORT" envDefault:""`
CertFile string `env:"SERVER_CERT" envDefault:""`
KeyFile string `env:"SERVER_KEY" envDefault:""`
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
ClientCAFile string `env:"CLIENT_CA_CERTS" envDefault:""`
AttestedTLS bool `env:"ATTESTED_TLS" envDefault:"false"`
}
type BaseServer struct {
+6 -4
View File
@@ -176,6 +176,8 @@ MANAGER_QEMU_SEV_CBITPOS=51 \
The kernel hash feature might not work with the current build of OVMF and QEMU. If so, build the host kernel, QEMU, and OVMF from the [AMD SEV GitHub](https://github.com/AMDESE/AMDSEV/tree/snp-latest) repository.
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 enable [AMD SEV-SNP](https://www.amd.com/en/developer/sev.html) support, start manager like this
```sh
@@ -185,7 +187,7 @@ 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 Amd Sev built package> \
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
./build/cocos-manager
```
@@ -203,10 +205,10 @@ MANAGER_QEMU_KERNEL_HASH=true \
### 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 Manager test server by executing (with the environment variables of choice):
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/manager-server/main.go
go run ./test/computations/main.go <dataset path> <algo path>
```
and in the second the manager by executing (with the environment variables of choice):
@@ -215,7 +217,7 @@ and in the second the manager by executing (with the environment variables of ch
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. The Manager test server is listening on the default value of the MANAGER_GRPC_PORT. In the last one, you can run the verification commands.
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:
+29 -12
View File
@@ -4,12 +4,15 @@ package grpc
import (
"context"
"errors"
"github.com/ultravioletrs/cocos/manager"
pkgmanager "github.com/ultravioletrs/cocos/pkg/manager"
"golang.org/x/sync/errgroup"
)
var errTerminationFromServer = errors.New("server requested client termination")
type ManagerClient struct {
stream pkgmanager.ManagerService_ProcessClient
svc manager.Service
@@ -25,7 +28,7 @@ func NewClient(stream pkgmanager.ManagerService_ProcessClient, svc manager.Servi
}
}
func (client ManagerClient) Process(ctx context.Context) error {
func (client ManagerClient) Process(ctx context.Context, cancel context.CancelFunc) error {
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
@@ -34,24 +37,38 @@ func (client ManagerClient) Process(ctx context.Context) error {
if err != nil {
return err
}
port, err := client.svc.Run(ctx, req)
if err != nil {
return err
}
runRes := &pkgmanager.ClientStreamMessage_RunRes{RunRes: &pkgmanager.RunResponse{AgentPort: port, ComputationId: req.Id}}
if err := client.stream.Send(&pkgmanager.ClientStreamMessage{Message: runRes}); err != nil {
return err
switch mes := req.Message.(type) {
case *pkgmanager.ServerStreamMessage_RunReq:
port, err := client.svc.Run(ctx, mes.RunReq)
if err != nil {
return err
}
runRes := &pkgmanager.ClientStreamMessage_RunRes{RunRes: &pkgmanager.RunResponse{AgentPort: port, ComputationId: mes.RunReq.Id}}
if err := client.stream.Send(&pkgmanager.ClientStreamMessage{Message: runRes}); err != nil {
return err
}
case *pkgmanager.ServerStreamMessage_TerminateReq:
cancel()
return errors.Join(errTerminationFromServer, errors.New(mes.TerminateReq.Message))
case *pkgmanager.ServerStreamMessage_StopComputation:
if err := client.svc.Stop(ctx, mes.StopComputation.ComputationId); err != nil {
return err
}
}
}
})
eg.Go(func() error {
for mes := range client.responses {
if err := client.stream.Send(mes); err != nil {
return err
for {
select {
case <-ctx.Done():
return nil
case mes := <-client.responses:
if err := client.stream.Send(mes); err != nil {
return err
}
}
}
return nil
})
return eg.Wait()
+16 -13
View File
@@ -3,40 +3,39 @@
package grpc
import (
"context"
"github.com/ultravioletrs/cocos/pkg/manager"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
)
var _ manager.ManagerServiceServer = (*grpcServer)(nil)
type grpcServer struct {
manager.UnimplementedManagerServiceServer
incoming chan *manager.ClientStreamMessage
svc Service
ctx context.Context
}
type Service interface {
Run(ipAddress string, runReqChan chan *manager.ComputationRunReq)
Run(ipAddress string, runReqChan chan *manager.ServerStreamMessage, authInfo credentials.AuthInfo)
}
// NewServer returns new AuthServiceServer instance.
func NewServer(ctx context.Context, incoming chan *manager.ClientStreamMessage, svc Service) manager.ManagerServiceServer {
func NewServer(incoming chan *manager.ClientStreamMessage, svc Service) manager.ManagerServiceServer {
return &grpcServer{
incoming: incoming,
svc: svc,
ctx: ctx,
}
}
func (s *grpcServer) Process(stream manager.ManagerService_ProcessServer) error {
runReqChan := make(chan *manager.ComputationRunReq)
runReqChan := make(chan *manager.ServerStreamMessage)
client, ok := peer.FromContext(stream.Context())
if ok {
go s.svc.Run(client.Addr.String(), runReqChan)
go s.svc.Run(client.Addr.String(), runReqChan, client.AuthInfo)
}
eg, _ := errgroup.WithContext(s.ctx)
eg, ctx := errgroup.WithContext(stream.Context())
eg.Go(func() error {
for {
@@ -50,12 +49,16 @@ func (s *grpcServer) Process(stream manager.ManagerService_ProcessServer) error
})
eg.Go(func() error {
for runReq := range runReqChan {
if err := stream.Send(runReq); err != nil {
return err
for {
select {
case <-ctx.Done():
return nil
case req := <-runReqChan:
if err := stream.Send(req); err != nil {
return err
}
}
}
return nil
})
return eg.Wait()
}
+13
View File
@@ -41,6 +41,19 @@ func (lm *loggingMiddleware) Run(ctx context.Context, mc *pkgmanager.Computation
return lm.svc.Run(ctx, mc)
}
func (lm *loggingMiddleware) Stop(ctx context.Context, computationID string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method Stop for computation took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(message)
}(time.Now())
return lm.svc.Stop(ctx, computationID)
}
func (lm *loggingMiddleware) RetrieveAgentEventsLogs() {
lm.svc.RetrieveAgentEventsLogs()
}
+9
View File
@@ -42,6 +42,15 @@ func (ms *metricsMiddleware) Run(ctx context.Context, mc *pkgmanager.Computation
return ms.svc.Run(ctx, mc)
}
func (ms *metricsMiddleware) Stop(ctx context.Context, computationID string) error {
defer func(begin time.Time) {
ms.counter.With("method", "Stop").Add(1)
ms.latency.With("method", "Stop").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Stop(ctx, computationID)
}
func (ms *metricsMiddleware) RetrieveAgentEventsLogs() {
ms.svc.RetrieveAgentEventsLogs()
}
+29 -9
View File
@@ -10,7 +10,15 @@ package manager;
option go_package = "./manager";
service ManagerService {
rpc Process(stream ClientStreamMessage) returns (stream ComputationRunReq) {}
rpc Process(stream ClientStreamMessage) returns (stream ServerStreamMessage) {}
}
message Terminate {
string message = 1;
}
message StopComputation {
string computation_id = 1;
}
message RunResponse{
@@ -42,26 +50,36 @@ message ClientStreamMessage {
}
}
message ServerStreamMessage {
oneof message {
ComputationRunReq runReq = 1;
Terminate terminateReq = 2;
StopComputation stopComputation = 3;
}
}
message ComputationRunReq {
string id = 1;
string name = 2;
string description = 3;
repeated Dataset datasets = 4;
repeated Algorithm algorithms = 5;
repeated string result_consumers = 6;
Algorithm algorithm = 5;
repeated ResultConsumer result_consumers = 6;
AgentConfig agent_config = 7;
}
message ResultConsumer {
bytes userKey = 1;
}
message Dataset {
string provider = 1;
string id = 2;
bytes hash = 3; // should be sha3.Sum256, 32 byte length.
bytes hash = 1; // should be sha3.Sum256, 32 byte length.
bytes userKey = 2;
}
message Algorithm {
string provider = 1;
string id = 2;
bytes hash = 3; // should be sha3.Sum256, 32 byte length.
bytes hash = 1; // should be sha3.Sum256, 32 byte length.
bytes userKey = 2;
}
message AgentConfig {
@@ -72,4 +90,6 @@ message AgentConfig {
string client_ca_file = 5;
string server_ca_file = 6;
string log_level = 7;
bool attested_tls = 8;
}
+232
View File
@@ -0,0 +1,232 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package manager
import (
"context"
"encoding/json"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/ultravioletrs/cocos/manager/qemu"
"github.com/ultravioletrs/cocos/manager/vm"
"github.com/ultravioletrs/cocos/manager/vm/mocks"
"github.com/ultravioletrs/cocos/pkg/manager"
)
func TestNew(t *testing.T) {
qemuCfg := qemu.Config{}
logger := slog.Default()
eventsChan := make(chan *manager.ClientStreamMessage)
vmf := new(mocks.Provider)
service := New(qemuCfg, logger, eventsChan, vmf.Execute)
assert.NotNil(t, service)
assert.IsType(t, &managerService{}, service)
}
func TestRun(t *testing.T) {
vmf := new(mocks.Provider)
vmMock := new(mocks.VM)
vmf.On("Execute", mock.Anything, mock.Anything, mock.Anything).Return(vmMock)
tests := []struct {
name string
req *manager.ComputationRunReq
vmStartError error
expectedError error
}{
{
name: "Successful run",
req: &manager.ComputationRunReq{
Id: "test-computation",
Name: "Test Computation",
Algorithm: &manager.Algorithm{
Hash: make([]byte, hashLength),
},
AgentConfig: &manager.AgentConfig{},
},
vmStartError: nil,
expectedError: nil,
},
{
name: "VM start failure",
req: &manager.ComputationRunReq{
Id: "test-computation",
Name: "Test Computation",
Algorithm: &manager.Algorithm{
Hash: make([]byte, hashLength),
},
AgentConfig: &manager.AgentConfig{},
},
vmStartError: assert.AnError,
expectedError: assert.AnError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.vmStartError == nil {
vmMock.On("Start").Return(nil).Once()
} else {
vmMock.On("Start").Return(tt.vmStartError).Once()
}
vmMock.On("SendAgentConfig", mock.Anything).Return(nil)
qemuCfg := qemu.Config{
VSockConfig: qemu.VSockConfig{
GuestCID: 3,
Vnc: 5900,
},
}
logger := slog.Default()
eventsChan := make(chan *manager.ClientStreamMessage, 10)
ms := &managerService{
qemuCfg: qemuCfg,
logger: logger,
agents: make(map[int]string),
vms: make(map[string]vm.VM),
eventsChan: eventsChan,
vmFactory: vmf.Execute,
}
ctx := context.Background()
port, err := ms.Run(ctx, tt.req)
if tt.expectedError != nil {
assert.Error(t, err)
assert.ErrorIs(t, err, tt.expectedError)
assert.Empty(t, port)
} else {
assert.NoError(t, err)
assert.NotEmpty(t, port)
assert.Len(t, ms.vms, 1)
assert.Len(t, ms.agents, 1)
}
vmf.AssertExpectations(t)
// Clear the events channel
for len(eventsChan) > 0 {
<-eventsChan
}
})
}
}
func TestStop(t *testing.T) {
tests := []struct {
name string
computationID string
vmStopError error
expectedError error
initialVMCount int
}{
{
name: "Successful stop",
computationID: "existing-computation",
vmStopError: nil,
expectedError: nil,
initialVMCount: 1,
},
{
name: "Non-existent computation",
computationID: "non-existent-computation",
vmStopError: nil,
expectedError: ErrNotFound,
initialVMCount: 0,
},
{
name: "VM stop error",
computationID: "error-computation",
vmStopError: assert.AnError,
expectedError: assert.AnError,
initialVMCount: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ms := &managerService{
vms: make(map[string]vm.VM),
}
vmMock := new(mocks.VM)
if tt.vmStopError == nil {
vmMock.On("Stop").Return(nil).Once()
} else {
vmMock.On("Stop").Return(assert.AnError).Once()
}
if tt.initialVMCount > 0 {
ms.vms[tt.computationID] = vmMock
}
err := ms.Stop(context.Background(), tt.computationID)
if tt.expectedError != nil {
assert.Error(t, err)
assert.ErrorIs(t, err, tt.expectedError)
} else {
assert.NoError(t, err)
assert.Len(t, ms.vms, 0)
}
})
}
}
func TestGetFreePort(t *testing.T) {
port, err := getFreePort()
assert.NoError(t, err)
assert.Greater(t, port, 0)
}
func TestPublishEvent(t *testing.T) {
tests := []struct {
name string
event string
computationID string
status string
details json.RawMessage
}{
{
name: "Standard event",
event: "test-event",
computationID: "test-computation",
status: "test-status",
details: nil,
},
{
name: "Event with details",
event: "detailed-event",
computationID: "detailed-computation",
status: "detailed-status",
details: json.RawMessage(`{"key": "value"}`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
eventsChan := make(chan *manager.ClientStreamMessage, 1)
ms := &managerService{
eventsChan: eventsChan,
}
ms.publishEvent(tt.event, tt.computationID, tt.status, tt.details)
assert.Len(t, eventsChan, 1)
event := <-eventsChan
assert.Equal(t, tt.event, event.GetAgentEvent().EventType)
assert.Equal(t, tt.computationID, event.GetAgentEvent().ComputationId)
assert.Equal(t, tt.status, event.GetAgentEvent().Status)
assert.Equal(t, "manager", event.GetAgentEvent().Originator)
assert.Equal(t, tt.details, json.RawMessage(event.GetAgentEvent().Details))
})
}
}
+33 -26
View File
@@ -8,24 +8,24 @@ import (
)
type MemoryConfig struct {
Size string `env:"MEMORY_SIZE" envDefault:"2048M"`
Size string `env:"MEMORY_SIZE" envDefault:"2048M"`
Slots int `env:"MEMORY_SLOTS" envDefault:"5"`
Max string `env:"MAX_MEMORY" envDefault:"30G"`
Max string `env:"MAX_MEMORY" envDefault:"30G"`
}
type OVMFCodeConfig struct {
If string `env:"OVMF_CODE_IF" envDefault:"pflash"`
Format string `env:"OVMF_CODE_FORMAT" envDefault:"raw"`
Unit int `env:"OVMF_CODE_UNIT" envDefault:"0"`
File string `env:"OVMF_CODE_FILE" envDefault:"/usr/share/OVMF/OVMF_CODE.fd"`
If string `env:"OVMF_CODE_IF" envDefault:"pflash"`
Format string `env:"OVMF_CODE_FORMAT" envDefault:"raw"`
Unit int `env:"OVMF_CODE_UNIT" envDefault:"0"`
File string `env:"OVMF_CODE_FILE" envDefault:"/usr/share/OVMF/OVMF_CODE.fd"`
ReadOnly string `env:"OVMF_CODE_READONLY" envDefault:"on"`
}
type OVMFVarsConfig struct {
If string `env:"OVMF_VARS_IF" envDefault:"pflash"`
If string `env:"OVMF_VARS_IF" envDefault:"pflash"`
Format string `env:"OVMF_VARS_FORMAT" envDefault:"raw"`
Unit int `env:"OVMF_VARS_UNIT" envDefault:"1"`
File string `env:"OVMF_VARS_FILE" envDefault:"/usr/share/OVMF/OVMF_VARS.fd"`
Unit int `env:"OVMF_VARS_UNIT" envDefault:"1"`
File string `env:"OVMF_VARS_FILE" envDefault:"/usr/share/OVMF/OVMF_VARS.fd"`
}
type NetDevConfig struct {
@@ -37,7 +37,7 @@ type NetDevConfig struct {
type VirtioNetPciConfig struct {
DisableLegacy string `env:"VIRTIO_NET_PCI_DISABLE_LEGACY" envDefault:"on"`
IOMMUPlatform bool `env:"VIRTIO_NET_PCI_IOMMU_PLATFORM" envDefault:"true"`
Addr string `env:"VIRTIO_NET_PCI_ADDR" envDefault:"0x2"`
Addr string `env:"VIRTIO_NET_PCI_ADDR" envDefault:"0x2"`
ROMFile string `env:"VIRTIO_NET_PCI_ROMFILE"`
}
@@ -47,32 +47,33 @@ type DiskImgConfig struct {
}
type SevConfig struct {
ID string `env:"SEV_ID" envDefault:"sev0"`
CBitPos int `env:"SEV_CBITPOS" envDefault:"51"`
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:""`
}
type VSockConfig struct {
ID string `env:"VSOCK_ID" envDefault:"vhost-vsock-pci0"`
GuestCID int `env:"VSOCK_GUEST_CID" envDefault:"3"`
vnc int `env:"VSOCK_VNC" envDefault:"0"`
Vnc int `env:"VSOCK_VNC" envDefault:"0"`
}
type Config struct {
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
TmpFileLoc string `env:"TMP_FILE_LOC" envDefault:"tmp"`
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
EnableSEV bool `env:"ENABLE_SEV" envDefault:"false"`
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
TmpFileLoc string `env:"TMP_FILE_LOC" envDefault:"tmp"`
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
EnableSEV bool `env:"ENABLE_SEV" envDefault:"false"`
EnableSEVSNP bool `env:"ENABLE_SEV_SNP" envDefault:"true"`
EnableKVM bool `env:"ENABLE_KVM" envDefault:"true"`
// machine, CPU, RAM
Machine string `env:"MACHINE" envDefault:"q35"`
CPU string `env:"CPU" envDefault:"EPYC"`
SMPCount int `env:"SMP_COUNT" envDefault:"4"`
Machine string `env:"MACHINE" envDefault:"q35"`
CPU string `env:"CPU" envDefault:"EPYC"`
SMPCount int `env:"SMP_COUNT" envDefault:"4"`
MaxCPUs int `env:"SMP_MAXCPUS" envDefault:"64"`
MemID string `env:"MEM_ID" envDefault:"ram1"`
MemID string `env:"MEM_ID" envDefault:"ram1"`
MemoryConfig
// Kernel hash
@@ -97,10 +98,10 @@ type Config struct {
// display
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
Monitor string `env:"MONITOR" envDefault:"pty"`
Monitor string `env:"MONITOR" envDefault:"pty"`
}
func constructQemuArgs(config Config) []string {
func (config Config) ConstructQemuArgs() []string {
args := []string{}
// virtualization
@@ -157,7 +158,7 @@ func constructQemuArgs(config Config) []string {
config.VirtioNetPciConfig.ROMFile))
args = append(args, "-device", fmt.Sprintf("vhost-vsock-pci,id=%s,guest-cid=%d", config.VSockConfig.ID, config.VSockConfig.GuestCID))
args = append(args, "-vnc", fmt.Sprintf(":%d", config.vnc))
args = append(args, "-vnc", fmt.Sprintf(":%d", config.Vnc))
if config.EnableSEVSNP {
args = append(args, "-object",
@@ -177,9 +178,14 @@ func constructQemuArgs(config Config) []string {
if config.EnableSEV || config.EnableSEVSNP {
sevType := "sev-guest"
kernelHash := ""
hostData := ""
if config.EnableSEVSNP {
sevType = "sev-snp-guest"
if config.SevConfig.HostData != "" {
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
}
}
if config.KernelHash {
@@ -187,12 +193,13 @@ func constructQemuArgs(config Config) []string {
}
args = append(args, "-object",
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s%s",
sevType,
config.SevConfig.ID,
config.SevConfig.CBitPos,
config.SevConfig.ReducedPhysBits,
kernelHash))
kernelHash,
hostData))
args = append(args, "-machine",
fmt.Sprintf("memory-encryption=%s", config.SevConfig.ID))
-91
View File
@@ -1,91 +0,0 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package qemu
import (
"context"
"fmt"
"os/exec"
"github.com/gofrs/uuid"
"github.com/ultravioletrs/cocos/internal"
)
const (
firmwareVars = "OVMF_VARS"
KernelFile = "bzImage"
rootfsFile = "rootfs.cpio"
)
func CreateVM(ctx context.Context, cfg Config) (*exec.Cmd, error) {
// Create unique emu device identifiers.
id, err := uuid.NewV4()
if err != nil {
return &exec.Cmd{}, err
}
qemuCfg := cfg
qemuCfg.NetDevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.NetDevConfig.ID, id)
qemuCfg.SevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.SevConfig.ID, id)
// Copy firmware vars file.
srcFile := qemuCfg.OVMFVarsConfig.File
dstFile := fmt.Sprintf("%s/%s-%s.fd", cfg.TmpFileLoc, firmwareVars, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return &exec.Cmd{}, err
}
qemuCfg.OVMFVarsConfig.File = dstFile
// Copy img files.
srcFile = qemuCfg.DiskImgConfig.KernelFile
dstFile = fmt.Sprintf("%s/%s-%s", cfg.TmpFileLoc, KernelFile, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return &exec.Cmd{}, err
}
qemuCfg.DiskImgConfig.KernelFile = dstFile
srcFile = qemuCfg.DiskImgConfig.RootFsFile
dstFile = fmt.Sprintf("%s/%s-%s.gz", cfg.TmpFileLoc, rootfsFile, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return &exec.Cmd{}, err
}
qemuCfg.DiskImgConfig.RootFsFile = dstFile
exe, args, err := ExecutableAndArgs(qemuCfg)
if err != nil {
return &exec.Cmd{}, err
}
cmd, err := runQemuVM(exe, args)
if err != nil {
return cmd, err
}
return cmd, nil
}
func ExecutableAndArgs(cfg Config) (string, []string, error) {
exe, err := exec.LookPath(cfg.QemuBinPath)
if err != nil {
return "", nil, err
}
args := constructQemuArgs(cfg)
if cfg.UseSudo {
args = append([]string{exe}, args...)
exe = "sudo"
}
return exe, args, nil
}
func runQemuVM(exe string, args []string) (*exec.Cmd, error) {
cmd, err := internal.RunCmdStart(exe, args...)
if err != nil {
return nil, err
}
return cmd, nil
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package qemu
import (
"fmt"
"os/exec"
"github.com/gofrs/uuid"
"github.com/ultravioletrs/cocos/internal"
"github.com/ultravioletrs/cocos/manager/vm"
"github.com/ultravioletrs/cocos/pkg/manager"
)
const (
firmwareVars = "OVMF_VARS"
KernelFile = "bzImage"
rootfsFile = "rootfs.cpio"
)
type qemuVM struct {
config Config
cmd *exec.Cmd
logsChan chan *manager.ClientStreamMessage
computationId string
}
func NewVM(config interface{}, logsChan chan *manager.ClientStreamMessage, computationId string) vm.VM {
return &qemuVM{
config: config.(Config),
logsChan: logsChan,
computationId: computationId,
}
}
func (v *qemuVM) Start() error {
// Create unique qemu device identifiers
id, err := uuid.NewV4()
if err != nil {
return err
}
qemuCfg := v.config
qemuCfg.NetDevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.NetDevConfig.ID, id)
qemuCfg.SevConfig.ID = fmt.Sprintf("%s-%s", qemuCfg.SevConfig.ID, id)
if !v.config.KernelHash {
// Copy firmware vars file
srcFile := qemuCfg.OVMFVarsConfig.File
dstFile := fmt.Sprintf("%s/%s-%s.fd", v.config.TmpFileLoc, firmwareVars, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return err
}
qemuCfg.OVMFVarsConfig.File = dstFile
}
// Copy img files
srcFile := qemuCfg.DiskImgConfig.KernelFile
dstFile := fmt.Sprintf("%s/%s-%s", v.config.TmpFileLoc, KernelFile, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return err
}
qemuCfg.DiskImgConfig.KernelFile = dstFile
srcFile = qemuCfg.DiskImgConfig.RootFsFile
dstFile = fmt.Sprintf("%s/%s-%s.gz", v.config.TmpFileLoc, rootfsFile, id)
err = internal.CopyFile(srcFile, dstFile)
if err != nil {
return err
}
qemuCfg.DiskImgConfig.RootFsFile = dstFile
exe, args, err := v.executableAndArgs()
if err != nil {
return err
}
v.cmd = exec.Command(exe, args...)
v.cmd.Stdout = &vm.Stdout{LogsChan: v.logsChan, ComputationId: v.computationId}
v.cmd.Stderr = &vm.Stderr{LogsChan: v.logsChan, ComputationId: v.computationId}
return v.cmd.Start()
}
func (v *qemuVM) Stop() error {
return v.cmd.Process.Kill()
}
func (v *qemuVM) executableAndArgs() (string, []string, error) {
exe, err := exec.LookPath(v.config.QemuBinPath)
if err != nil {
return "", nil, err
}
args := v.config.ConstructQemuArgs()
if v.config.UseSudo {
args = append([]string{exe}, args...)
exe = "sudo"
}
return exe, args, nil
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package qemu
import (
"os/exec"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ultravioletrs/cocos/pkg/manager"
)
func TestNewVM(t *testing.T) {
config := Config{}
logsChan := make(chan *manager.ClientStreamMessage)
computationId := "test-computation"
nvm := NewVM(config, logsChan, computationId)
assert.NotNil(t, nvm)
assert.IsType(t, &qemuVM{}, nvm)
}
func TestVM_Stop(t *testing.T) {
// Setup
v := &qemuVM{
cmd: exec.Command("sleep", "1"),
}
err := v.cmd.Start()
assert.NoError(t, err)
// Test
err = v.Stop()
// Assert
assert.NoError(t, err)
assert.Error(t, v.cmd.Wait()) // Process should have been killed
}
+3 -3
View File
@@ -1,6 +1,6 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package manager
package qemu
import (
"encoding/json"
@@ -11,8 +11,8 @@ import (
const VsockConfigPort uint32 = 9999
func SendAgentConfig(cid uint32, ac agent.Computation) error {
conn, err := vsock.Dial(cid, VsockConfigPort, nil)
func (v *qemuVM) SendAgentConfig(ac agent.Computation) error {
conn, err := vsock.Dial(uint32(v.config.GuestCID), VsockConfigPort, nil)
if err != nil {
return err
}
+58 -15
View File
@@ -4,6 +4,7 @@ package manager
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
@@ -14,7 +15,9 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/manager/qemu"
"github.com/ultravioletrs/cocos/manager/vm"
"github.com/ultravioletrs/cocos/pkg/manager"
"golang.org/x/crypto/sha3"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -36,6 +39,9 @@ var (
ErrFailedToAllocatePort = errors.New("failed to allocate free port on host")
errInvalidHashLength = errors.New("hash must be of byte length 32")
// ErrFailedToCalculateHash indicates that agent computation returned an error while calculating the hash of the computation.
ErrFailedToCalculateHash = errors.New("error while calculating the hash of the computation")
)
// Service specifies an API that must be fulfilled by the domain service
@@ -43,6 +49,8 @@ var (
type Service interface {
// Run create a computation.
Run(ctx context.Context, c *manager.ComputationRunReq) (string, error)
// Stop stops a computation.
Stop(ctx context.Context, computationID string) error
// RetrieveAgentEventsLogs Retrieve and forward agent logs and events via vsock.
RetrieveAgentEventsLogs()
}
@@ -52,17 +60,21 @@ type managerService struct {
logger *slog.Logger
agents map[int]string // agent map of vsock cid to computationID.
eventsChan chan *manager.ClientStreamMessage
vms map[string]vm.VM
vmFactory vm.Provider
}
var _ Service = (*managerService)(nil)
// New instantiates the manager service implementation.
func New(qemuCfg qemu.Config, logger *slog.Logger, eventsChan chan *manager.ClientStreamMessage) Service {
func New(qemuCfg qemu.Config, logger *slog.Logger, eventsChan chan *manager.ClientStreamMessage, vmFactory vm.Provider) Service {
ms := &managerService{
qemuCfg: qemuCfg,
logger: logger,
agents: make(map[int]string),
vms: make(map[string]vm.VM),
eventsChan: eventsChan,
vmFactory: vmFactory,
}
return ms
}
@@ -70,10 +82,9 @@ func New(qemuCfg qemu.Config, logger *slog.Logger, eventsChan chan *manager.Clie
func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq) (string, error) {
ms.publishEvent("vm-provision", c.Id, "starting", json.RawMessage{})
ac := agent.Computation{
ID: c.Id,
Name: c.Name,
Description: c.Description,
ResultConsumers: c.ResultConsumers,
ID: c.Id,
Name: c.Name,
Description: c.Description,
AgentConfig: agent.AgentConfig{
Port: c.AgentConfig.Port,
Host: c.AgentConfig.Host,
@@ -84,19 +95,18 @@ func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq)
LogLevel: c.AgentConfig.LogLevel,
},
}
for _, algo := range c.Algorithms {
if len(algo.Hash) != hashLength {
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
return "", errInvalidHashLength
}
ac.Algorithms = append(ac.Algorithms, agent.Algorithm{ID: algo.Id, Provider: algo.Provider, Hash: [hashLength]byte(algo.Hash)})
}
ac.Algorithm = agent.Algorithm{Hash: [hashLength]byte(c.Algorithm.Hash), UserKey: c.Algorithm.UserKey}
for _, data := range c.Datasets {
if len(data.Hash) != hashLength {
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
return "", errInvalidHashLength
}
ac.Datasets = append(ac.Datasets, agent.Dataset{ID: data.Id, Provider: data.Provider, Hash: [hashLength]byte(data.Hash)})
ac.Datasets = append(ac.Datasets, agent.Dataset{Hash: [hashLength]byte(data.Hash), UserKey: data.UserKey})
}
for _, rc := range c.ResultConsumers {
ac.ResultConsumers = append(ac.ResultConsumers, agent.ResultConsumer{UserKey: rc.UserKey})
}
agentPort, err := getFreePort()
@@ -106,26 +116,50 @@ func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq)
}
ms.qemuCfg.HostFwdAgent = agentPort
ch, err := computationHash(ac)
if err != nil {
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
return "", errors.Wrap(ErrFailedToCalculateHash, err)
}
// Define host-data value of QEMU for SEV-SNP, with a base64 encoding of the computation hash.
ms.qemuCfg.SevConfig.HostData = base64.StdEncoding.EncodeToString(ch[:])
cvm := ms.vmFactory(ms.qemuCfg, ms.eventsChan, c.Id)
ms.publishEvent("vm-provision", c.Id, "in-progress", json.RawMessage{})
if _, err = qemu.CreateVM(ctx, ms.qemuCfg); err != nil {
if err = cvm.Start(); err != nil {
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
return "", err
}
ms.vms[c.Id] = cvm
ms.agents[ms.qemuCfg.VSockConfig.GuestCID] = c.Id
err = backoff.Retry(func() error {
return SendAgentConfig(uint32(ms.qemuCfg.VSockConfig.GuestCID), ac)
return cvm.SendAgentConfig(ac)
}, backoff.NewExponentialBackOff())
if err != nil {
return "", err
}
ms.qemuCfg.VSockConfig.GuestCID++
ms.qemuCfg.VSockConfig.Vnc++
ms.publishEvent("vm-provision", c.Id, "complete", json.RawMessage{})
return fmt.Sprint(ms.qemuCfg.HostFwdAgent), nil
}
func (ms *managerService) Stop(ctx context.Context, computationID string) error {
cvm, ok := ms.vms[computationID]
if !ok {
return ErrNotFound
}
if err := cvm.Stop(); err != nil {
return err
}
delete(ms.vms, computationID)
return nil
}
func getFreePort() (int, error) {
listener, err := net.Listen("tcp", "")
if err != nil {
@@ -157,3 +191,12 @@ func (ms *managerService) publishEvent(event, cmpID, status string, details json
},
}
}
func computationHash(ac agent.Computation) ([32]byte, error) {
jsonData, err := json.Marshal(ac)
if err != nil {
return [32]byte{}, err
}
return sha3.Sum256(jsonData), nil
}
+7
View File
@@ -29,6 +29,13 @@ func (tm *tracingMiddleware) Run(ctx context.Context, mc *pkgmanager.Computation
return tm.svc.Run(ctx, mc)
}
func (tm *tracingMiddleware) Stop(ctx context.Context, computationID string) error {
ctx, span := tm.tracer.Start(ctx, "stop")
defer span.End()
return tm.svc.Stop(ctx, computationID)
}
func (tm *tracingMiddleware) RetrieveAgentEventsLogs() {
tm.svc.RetrieveAgentEventsLogs()
}
+133
View File
@@ -0,0 +1,133 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package vm
import (
"bytes"
"errors"
"io"
"github.com/ultravioletrs/cocos/pkg/manager"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
_ io.Writer = &Stdout{}
_ io.Writer = &Stderr{}
ErrFailedToSendMessage = errors.New("failed to send message to channel")
ErrPanicRecovered = errors.New("panic recovered: channel may be closed")
)
const bufSize = 1024
type Stdout struct {
LogsChan chan *manager.ClientStreamMessage
ComputationId string
}
// safeSend safely sends a message to the channel and returns an error on failure.
func safeSend(ch chan *manager.ClientStreamMessage, msg *manager.ClientStreamMessage) (err error) {
defer func() {
if r := recover(); r != nil {
// Recover from panic if the channel is closed
err = ErrPanicRecovered
}
}()
select {
case ch <- msg:
return nil
default:
// Channel is full or closed
return ErrFailedToSendMessage
}
}
// 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
}
msg := &manager.ClientStreamMessage{
Message: &manager.ClientStreamMessage_AgentLog{
AgentLog: &manager.AgentLog{
Message: string(buf[:n]),
ComputationId: s.ComputationId,
Level: "debug",
Timestamp: timestamppb.Now(),
},
},
}
if err := safeSend(s.LogsChan, msg); err != nil {
return len(p) - inBuf.Len(), err
}
}
return len(p), nil
}
type Stderr struct {
LogsChan chan *manager.ClientStreamMessage
ComputationId string
}
// 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
}
msg := &manager.ClientStreamMessage{
Message: &manager.ClientStreamMessage_AgentLog{
AgentLog: &manager.AgentLog{
Message: string(buf[:n]),
ComputationId: s.ComputationId,
Level: "error",
Timestamp: timestamppb.Now(),
},
},
}
if err := safeSend(s.LogsChan, msg); err != nil {
return len(p) - inBuf.Len(), err
}
}
// Ensure vm-provision failure message is sent
eventMsg := &manager.ClientStreamMessage{
Message: &manager.ClientStreamMessage_AgentEvent{
AgentEvent: &manager.AgentEvent{
ComputationId: s.ComputationId,
EventType: "vm-provision",
Timestamp: timestamppb.Now(),
Originator: "manager",
Status: "failed",
},
},
}
if err := safeSend(s.LogsChan, eventMsg); err != nil {
return len(p), err
}
return len(p), nil
}
+171
View File
@@ -0,0 +1,171 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package vm
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/ultravioletrs/cocos/pkg/manager"
)
func TestStdoutWrite(t *testing.T) {
tests := []struct {
name string
input string
expectedWrites int
}{
{
name: "Single write within buffer size",
input: "Hello, World!",
expectedWrites: 1,
},
{
name: "Multiple writes within buffer size",
input: "This is a longer message that will be split into multiple writes.",
expectedWrites: 1,
},
{
name: "Large write exceeding buffer size",
input: string(make([]byte, bufSize*2+1)),
expectedWrites: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logsChan := make(chan *manager.ClientStreamMessage, 10)
s := &Stdout{
LogsChan: logsChan,
ComputationId: "test-computation",
}
n, err := s.Write([]byte(tt.input))
assert.NoError(t, err)
assert.Equal(t, len(tt.input), n)
var receivedWrites int
for i := 0; i < tt.expectedWrites; i++ {
select {
case msg := <-logsChan:
receivedWrites++
agentLog := msg.GetAgentLog()
assert.NotNil(t, agentLog)
assert.Equal(t, "test-computation", agentLog.ComputationId)
assert.Equal(t, "debug", agentLog.Level)
assert.NotEmpty(t, agentLog.Message)
assert.NotNil(t, agentLog.Timestamp)
case <-time.After(time.Second):
t.Fatal("Timed out waiting for log message")
}
}
assert.Equal(t, tt.expectedWrites, receivedWrites)
})
}
}
func TestStderrWrite(t *testing.T) {
tests := []struct {
name string
input string
expectedWrites int
}{
{
name: "Single write within buffer size",
input: "Error: Something went wrong",
expectedWrites: 1,
},
{
name: "Multiple writes within buffer size",
input: "This is a longer error message that will be split into multiple writes.",
expectedWrites: 1,
},
{
name: "Large write exceeding buffer size",
input: string(make([]byte, bufSize*2)),
expectedWrites: 3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logsChan := make(chan *manager.ClientStreamMessage, 10)
s := &Stderr{
LogsChan: logsChan,
ComputationId: "test-computation",
}
n, err := s.Write([]byte(tt.input))
assert.NoError(t, err)
assert.Equal(t, len(tt.input), n)
var receivedWrites int
for i := 0; i < tt.expectedWrites; i++ {
select {
case msg := <-logsChan:
receivedWrites++
switch msg.Message.(type) {
case *manager.ClientStreamMessage_AgentLog:
agentLog := msg.GetAgentLog()
assert.NotNil(t, agentLog)
assert.Equal(t, "test-computation", agentLog.ComputationId)
assert.Equal(t, "error", agentLog.Level)
assert.NotEmpty(t, agentLog.Message)
assert.NotNil(t, agentLog.Timestamp)
case *manager.ClientStreamMessage_AgentEvent:
agentEvent := msg.GetAgentEvent()
assert.NotNil(t, agentEvent)
assert.Equal(t, "test-computation", agentEvent.ComputationId)
assert.Equal(t, "vm-provision", agentEvent.EventType)
assert.Equal(t, "failed", agentEvent.Status)
assert.NotNil(t, agentEvent.Timestamp)
}
case <-time.After(time.Second):
t.Fatal("Timed out waiting for log message")
}
}
assert.Equal(t, tt.expectedWrites, receivedWrites)
})
}
}
func TestStdoutWriteErrorHandling(t *testing.T) {
logsChan := make(chan *manager.ClientStreamMessage, 1)
s := &Stdout{
LogsChan: logsChan,
ComputationId: "test-computation",
}
// Test with a closed channel to simulate an error condition
close(logsChan)
message := []byte("This should fail")
n, err := s.Write(message)
assert.Error(t, err)
assert.Equal(t, len(message), n)
assert.Equal(t, ErrPanicRecovered, err)
}
func TestStderrWriteErrorHandling(t *testing.T) {
logsChan := make(chan *manager.ClientStreamMessage, 1)
s := &Stderr{
LogsChan: logsChan,
ComputationId: "test-computation",
}
// Test with a closed channel to simulate an error condition
close(logsChan)
message := []byte("This should fail")
n, err := s.Write(message)
assert.Error(t, err)
assert.Equal(t, len(message), n)
assert.Equal(t, ErrPanicRecovered, err)
}
+52
View File
@@ -0,0 +1,52 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
mock "github.com/stretchr/testify/mock"
manager "github.com/ultravioletrs/cocos/pkg/manager"
vm "github.com/ultravioletrs/cocos/manager/vm"
)
// Provider is an autogenerated mock type for the Provider type
type Provider struct {
mock.Mock
}
// Execute provides a mock function with given fields: config, logsChan, computationId
func (_m *Provider) Execute(config interface{}, logsChan chan *manager.ClientStreamMessage, computationId string) vm.VM {
ret := _m.Called(config, logsChan, computationId)
if len(ret) == 0 {
panic("no return value specified for Execute")
}
var r0 vm.VM
if rf, ok := ret.Get(0).(func(interface{}, chan *manager.ClientStreamMessage, string) vm.VM); ok {
r0 = rf(config, logsChan, computationId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(vm.VM)
}
}
return r0
}
// NewProvider creates a new instance of Provider. 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 NewProvider(t interface {
mock.TestingT
Cleanup(func())
}) *Provider {
mock := &Provider{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+84
View File
@@ -0,0 +1,84 @@
// Code generated by mockery v2.43.2. DO NOT EDIT.
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
mock "github.com/stretchr/testify/mock"
agent "github.com/ultravioletrs/cocos/agent"
)
// VM is an autogenerated mock type for the VM type
type VM struct {
mock.Mock
}
// SendAgentConfig provides a mock function with given fields: ac
func (_m *VM) SendAgentConfig(ac agent.Computation) error {
ret := _m.Called(ac)
if len(ret) == 0 {
panic("no return value specified for SendAgentConfig")
}
var r0 error
if rf, ok := ret.Get(0).(func(agent.Computation) error); ok {
r0 = rf(ac)
} else {
r0 = ret.Error(0)
}
return r0
}
// Start provides a mock function with given fields:
func (_m *VM) Start() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Start")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// Stop provides a mock function with given fields:
func (_m *VM) Stop() error {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for Stop")
}
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// NewVM creates a new instance of VM. 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 NewVM(t interface {
mock.TestingT
Cleanup(func())
}) *VM {
mock := &VM{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package vm
import (
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/pkg/manager"
)
// VM represents a virtual machine.
//
//go:generate mockery --name VM --output=./mocks --filename vm.go --quiet --note "Copyright (c) Ultraviolet \n // SPDX-License-Identifier: Apache-2.0"
type VM interface {
Start() error
Stop() error
SendAgentConfig(ac agent.Computation) error
}
//go:generate mockery --name Provider --output=./mocks --filename provider.go --quiet --note "Copyright (c) Ultraviolet \n // SPDX-License-Identifier: Apache-2.0"
type Provider func(config interface{}, logsChan chan *manager.ClientStreamMessage, computationId string) VM
+1 -2
View File
@@ -4,7 +4,6 @@ package agent
import (
"github.com/ultravioletrs/cocos/agent"
agentapi "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
)
@@ -15,5 +14,5 @@ func NewAgentClient(cfg grpc.Config) (grpc.Client, agent.AgentServiceClient, err
return nil, nil, err
}
return client, agentapi.NewClient(client.Connection(), cfg.Timeout), nil
return client, agent.NewAgentServiceClient(client.Connection()), nil
}
+205 -25
View File
@@ -5,12 +5,22 @@ package grpc
import (
"crypto/tls"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"fmt"
"os"
"path"
"time"
"github.com/absmach/magistrala/pkg/errors"
"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/google/go-sev-guest/validate"
"github.com/google/go-sev-guest/verify"
"github.com/google/go-sev-guest/verify/trust"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
@@ -24,9 +34,30 @@ const (
withmTLS
)
const (
cocosDirectory = ".cocos"
caBundleName = "ask_ark.pem"
productNameMilan = "Milan"
productNameGenoa = "Genoa"
)
var (
errGrpcConnect = errors.New("failed to connect to grpc server")
errGrpcClose = errors.New("failed to close grpc connection")
errGrpcConnect = errors.New("failed to connect to grpc server")
errGrpcClose = errors.New("failed to close grpc connection")
errManifestOpen = errors.New("failed to open Manifest")
errManifestMissing = errors.New("failed due to missing Manifest")
errManifestDecode = errors.New("failed to decode Manifest json")
errCertificateParse = errors.New("failed to parse x509 certificate")
errAttVerification = errors.New("attestation verification failed")
errAttValidation = errors.New("attestation validation failed")
errCustomExtension = errors.New("failed due to missing custom extension")
)
var (
customSEVSNPExtensionOID = asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6}
attestationConfiguration = AttestationConfiguration{}
timeout = time.Minute * 2
maxTryDelay = time.Second * 30
)
type Config struct {
@@ -35,6 +66,13 @@ type Config struct {
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
URL string `env:"URL" envDefault:"localhost:7001"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
AttestedTLS bool `env:"ATTESTED_TLS" envDefault:"false"`
Manifest string `env:"MANIFEST" envDefault:""`
}
type AttestationConfiguration struct {
SNPPolicy *check.Policy `json:"snp_policy,omitempty"`
RootOfTrust *check.RootOfTrust `json:"root_of_trust,omitempty"`
}
type Client interface {
@@ -102,34 +140,47 @@ func connect(cfg Config) (*grpc.ClientConn, security, error) {
secure := withoutTLS
tc := insecure.NewCredentials()
if cfg.ServerCAFile != "" {
tlsConfig := &tls.Config{}
// Loading root ca certificates file
rootCA, err := os.ReadFile(cfg.ServerCAFile)
if cfg.AttestedTLS {
err := ReadManifest(cfg.Manifest, &attestationConfiguration)
if err != nil {
return nil, secure, fmt.Errorf("failed to load root ca file: %w", err)
}
if len(rootCA) > 0 {
capool := x509.NewCertPool()
if !capool.AppendCertsFromPEM(rootCA) {
return nil, secure, fmt.Errorf("failed to append root ca to tls.Config")
}
tlsConfig.RootCAs = capool
secure = withTLS
return nil, secure, fmt.Errorf("failed to read Manifest %w", err)
}
// Loading mtls certificates file
if cfg.ClientCert != "" || cfg.ClientKey != "" {
certificate, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
if err != nil {
return nil, secure, fmt.Errorf("failed to client certificate and key %w", err)
}
tlsConfig.Certificates = []tls.Certificate{certificate}
secure = withmTLS
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: verifyAttestationReportTLS,
}
tc = credentials.NewTLS(tlsConfig)
} else {
if cfg.ServerCAFile != "" {
tlsConfig := &tls.Config{}
// Loading root ca certificates file
rootCA, err := os.ReadFile(cfg.ServerCAFile)
if err != nil {
return nil, secure, fmt.Errorf("failed to load root ca file: %w", err)
}
if len(rootCA) > 0 {
capool := x509.NewCertPool()
if !capool.AppendCertsFromPEM(rootCA) {
return nil, secure, fmt.Errorf("failed to append root ca to tls.Config")
}
tlsConfig.RootCAs = capool
secure = withTLS
}
// Loading mTLS certificates file
if cfg.ClientCert != "" || cfg.ClientKey != "" {
certificate, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
if err != nil {
return nil, secure, fmt.Errorf("failed to client certificate and key %w", err)
}
tlsConfig.Certificates = []tls.Certificate{certificate}
secure = withmTLS
}
tc = credentials.NewTLS(tlsConfig)
}
}
opts = append(opts, grpc.WithTransportCredentials(tc))
@@ -140,3 +191,132 @@ func connect(cfg Config) (*grpc.ClientConn, security, error) {
}
return conn, secure, nil
}
func ReadManifest(manifestPath string, attestationConfiguration *AttestationConfiguration) error {
if manifestPath != "" {
manifest, err := os.Open(manifestPath)
if err != nil {
return errors.Wrap(errManifestOpen, err)
}
defer manifest.Close()
decoder := json.NewDecoder(manifest)
err = decoder.Decode(attestationConfiguration)
if err != nil {
return errors.Wrap(errManifestDecode, err)
}
return nil
}
return errManifestMissing
}
func verifyAttestationReportTLS(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return errors.Wrap(errCertificateParse, err)
}
for _, ext := range cert.Extensions {
if ext.Id.Equal(customSEVSNPExtensionOID) {
// Check if the certificate is self-signed
err := checkIfCertificateSelfSigned(cert)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
publicKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
expectedReportData := sha3.Sum512(publicKeyBytes)
attestationConfiguration.SNPPolicy.ReportData = expectedReportData[:]
// Attestation verification and validation
sopts, err := verify.RootOfTrustToOptions(attestationConfiguration.RootOfTrust)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
sopts.Product = attestationConfiguration.SNPPolicy.Product
sopts.Getter = &trust.RetryHTTPSGetter{
Timeout: timeout,
MaxRetryDelay: maxTryDelay,
Getter: &trust.SimpleHTTPSGetter{},
}
attestationPB, err := abi.ReportCertsToProto(ext.Value)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
if err := fillInAttestationLocal(attestationPB); err != nil {
return err
}
if err = verify.SnpAttestation(attestationPB, sopts); err != nil {
return errors.Wrap(errAttVerification, err)
}
opts, err := validate.PolicyToOptions(attestationConfiguration.SNPPolicy)
if err != nil {
return errors.Wrap(errAttVerification, err)
}
if err = validate.SnpAttestation(attestationPB, opts); err != nil {
return errors.Wrap(errAttValidation, err)
}
return nil
}
}
return errCustomExtension
}
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
}
func fillInAttestationLocal(attestation *sevsnp.Attestation) error {
product := attestationConfiguration.RootOfTrust.Product
chain := attestation.GetCertificateChain()
if chain == nil {
chain = &sevsnp.CertificateChain{}
attestation.CertificateChain = chain
}
if len(chain.GetAskCert()) == 0 || len(chain.GetArkCert()) == 0 {
homePath, err := os.UserHomeDir()
if err != nil {
return err
}
bundleFilePath := path.Join(homePath, cocosDirectory, product, caBundleName)
if _, err := os.Stat(bundleFilePath); err == nil {
amdRootCerts := trust.AMDRootCerts{}
if err := amdRootCerts.FromKDSCert(bundleFilePath); err != nil {
return err
}
chain.ArkCert = amdRootCerts.ProductCerts.Ark.Raw
chain.AskCert = amdRootCerts.ProductCerts.Ask.Raw
}
}
return nil
}
+507 -194
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.31.0
// protoc v4.25.3
// protoc-gen-go v1.34.2
// protoc v5.27.2
// source: manager/manager.proto
package manager
@@ -24,6 +24,100 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Terminate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *Terminate) Reset() {
*x = Terminate{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Terminate) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Terminate) ProtoMessage() {}
func (x *Terminate) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Terminate.ProtoReflect.Descriptor instead.
func (*Terminate) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{0}
}
func (x *Terminate) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type StopComputation struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ComputationId string `protobuf:"bytes,1,opt,name=computation_id,json=computationId,proto3" json:"computation_id,omitempty"`
}
func (x *StopComputation) Reset() {
*x = StopComputation{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StopComputation) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StopComputation) ProtoMessage() {}
func (x *StopComputation) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StopComputation.ProtoReflect.Descriptor instead.
func (*StopComputation) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{1}
}
func (x *StopComputation) GetComputationId() string {
if x != nil {
return x.ComputationId
}
return ""
}
type RunResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -36,7 +130,7 @@ type RunResponse struct {
func (x *RunResponse) Reset() {
*x = RunResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[0]
mi := &file_manager_manager_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -49,7 +143,7 @@ func (x *RunResponse) String() string {
func (*RunResponse) ProtoMessage() {}
func (x *RunResponse) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[0]
mi := &file_manager_manager_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -62,7 +156,7 @@ func (x *RunResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RunResponse.ProtoReflect.Descriptor instead.
func (*RunResponse) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{0}
return file_manager_manager_proto_rawDescGZIP(), []int{2}
}
func (x *RunResponse) GetAgentPort() string {
@@ -95,7 +189,7 @@ type AgentEvent struct {
func (x *AgentEvent) Reset() {
*x = AgentEvent{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[1]
mi := &file_manager_manager_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -108,7 +202,7 @@ func (x *AgentEvent) String() string {
func (*AgentEvent) ProtoMessage() {}
func (x *AgentEvent) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[1]
mi := &file_manager_manager_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -121,7 +215,7 @@ func (x *AgentEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentEvent.ProtoReflect.Descriptor instead.
func (*AgentEvent) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{1}
return file_manager_manager_proto_rawDescGZIP(), []int{3}
}
func (x *AgentEvent) GetEventType() string {
@@ -180,7 +274,7 @@ type AgentLog struct {
func (x *AgentLog) Reset() {
*x = AgentLog{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[2]
mi := &file_manager_manager_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -193,7 +287,7 @@ func (x *AgentLog) String() string {
func (*AgentLog) ProtoMessage() {}
func (x *AgentLog) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[2]
mi := &file_manager_manager_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -206,7 +300,7 @@ func (x *AgentLog) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentLog.ProtoReflect.Descriptor instead.
func (*AgentLog) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{2}
return file_manager_manager_proto_rawDescGZIP(), []int{4}
}
func (x *AgentLog) GetMessage() string {
@@ -253,7 +347,7 @@ type ClientStreamMessage struct {
func (x *ClientStreamMessage) Reset() {
*x = ClientStreamMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[3]
mi := &file_manager_manager_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -266,7 +360,7 @@ func (x *ClientStreamMessage) String() string {
func (*ClientStreamMessage) ProtoMessage() {}
func (x *ClientStreamMessage) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[3]
mi := &file_manager_manager_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -279,7 +373,7 @@ func (x *ClientStreamMessage) ProtoReflect() protoreflect.Message {
// Deprecated: Use ClientStreamMessage.ProtoReflect.Descriptor instead.
func (*ClientStreamMessage) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{3}
return file_manager_manager_proto_rawDescGZIP(), []int{5}
}
func (m *ClientStreamMessage) GetMessage() isClientStreamMessage_Message {
@@ -332,24 +426,119 @@ func (*ClientStreamMessage_AgentEvent) isClientStreamMessage_Message() {}
func (*ClientStreamMessage_RunRes) isClientStreamMessage_Message() {}
type ServerStreamMessage struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Message:
//
// *ServerStreamMessage_RunReq
// *ServerStreamMessage_TerminateReq
// *ServerStreamMessage_StopComputation
Message isServerStreamMessage_Message `protobuf_oneof:"message"`
}
func (x *ServerStreamMessage) Reset() {
*x = ServerStreamMessage{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ServerStreamMessage) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ServerStreamMessage) ProtoMessage() {}
func (x *ServerStreamMessage) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ServerStreamMessage.ProtoReflect.Descriptor instead.
func (*ServerStreamMessage) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{6}
}
func (m *ServerStreamMessage) GetMessage() isServerStreamMessage_Message {
if m != nil {
return m.Message
}
return nil
}
func (x *ServerStreamMessage) GetRunReq() *ComputationRunReq {
if x, ok := x.GetMessage().(*ServerStreamMessage_RunReq); ok {
return x.RunReq
}
return nil
}
func (x *ServerStreamMessage) GetTerminateReq() *Terminate {
if x, ok := x.GetMessage().(*ServerStreamMessage_TerminateReq); ok {
return x.TerminateReq
}
return nil
}
func (x *ServerStreamMessage) GetStopComputation() *StopComputation {
if x, ok := x.GetMessage().(*ServerStreamMessage_StopComputation); ok {
return x.StopComputation
}
return nil
}
type isServerStreamMessage_Message interface {
isServerStreamMessage_Message()
}
type ServerStreamMessage_RunReq struct {
RunReq *ComputationRunReq `protobuf:"bytes,1,opt,name=runReq,proto3,oneof"`
}
type ServerStreamMessage_TerminateReq struct {
TerminateReq *Terminate `protobuf:"bytes,2,opt,name=terminateReq,proto3,oneof"`
}
type ServerStreamMessage_StopComputation struct {
StopComputation *StopComputation `protobuf:"bytes,3,opt,name=stopComputation,proto3,oneof"`
}
func (*ServerStreamMessage_RunReq) isServerStreamMessage_Message() {}
func (*ServerStreamMessage_TerminateReq) isServerStreamMessage_Message() {}
func (*ServerStreamMessage_StopComputation) isServerStreamMessage_Message() {}
type ComputationRunReq struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
Datasets []*Dataset `protobuf:"bytes,4,rep,name=datasets,proto3" json:"datasets,omitempty"`
Algorithms []*Algorithm `protobuf:"bytes,5,rep,name=algorithms,proto3" json:"algorithms,omitempty"`
ResultConsumers []string `protobuf:"bytes,6,rep,name=result_consumers,json=resultConsumers,proto3" json:"result_consumers,omitempty"`
AgentConfig *AgentConfig `protobuf:"bytes,7,opt,name=agent_config,json=agentConfig,proto3" json:"agent_config,omitempty"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
Datasets []*Dataset `protobuf:"bytes,4,rep,name=datasets,proto3" json:"datasets,omitempty"`
Algorithm *Algorithm `protobuf:"bytes,5,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
ResultConsumers []*ResultConsumer `protobuf:"bytes,6,rep,name=result_consumers,json=resultConsumers,proto3" json:"result_consumers,omitempty"`
AgentConfig *AgentConfig `protobuf:"bytes,7,opt,name=agent_config,json=agentConfig,proto3" json:"agent_config,omitempty"`
}
func (x *ComputationRunReq) Reset() {
*x = ComputationRunReq{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[4]
mi := &file_manager_manager_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -362,7 +551,7 @@ func (x *ComputationRunReq) String() string {
func (*ComputationRunReq) ProtoMessage() {}
func (x *ComputationRunReq) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[4]
mi := &file_manager_manager_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -375,7 +564,7 @@ func (x *ComputationRunReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use ComputationRunReq.ProtoReflect.Descriptor instead.
func (*ComputationRunReq) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{4}
return file_manager_manager_proto_rawDescGZIP(), []int{7}
}
func (x *ComputationRunReq) GetId() string {
@@ -406,14 +595,14 @@ func (x *ComputationRunReq) GetDatasets() []*Dataset {
return nil
}
func (x *ComputationRunReq) GetAlgorithms() []*Algorithm {
func (x *ComputationRunReq) GetAlgorithm() *Algorithm {
if x != nil {
return x.Algorithms
return x.Algorithm
}
return nil
}
func (x *ComputationRunReq) GetResultConsumers() []string {
func (x *ComputationRunReq) GetResultConsumers() []*ResultConsumer {
if x != nil {
return x.ResultConsumers
}
@@ -427,20 +616,66 @@ func (x *ComputationRunReq) GetAgentConfig() *AgentConfig {
return nil
}
type ResultConsumer struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserKey []byte `protobuf:"bytes,1,opt,name=userKey,proto3" json:"userKey,omitempty"`
}
func (x *ResultConsumer) Reset() {
*x = ResultConsumer{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ResultConsumer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResultConsumer) ProtoMessage() {}
func (x *ResultConsumer) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResultConsumer.ProtoReflect.Descriptor instead.
func (*ResultConsumer) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{8}
}
func (x *ResultConsumer) GetUserKey() []byte {
if x != nil {
return x.UserKey
}
return nil
}
type Dataset struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
Hash []byte `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length.
Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length.
UserKey []byte `protobuf:"bytes,2,opt,name=userKey,proto3" json:"userKey,omitempty"`
}
func (x *Dataset) Reset() {
*x = Dataset{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[5]
mi := &file_manager_manager_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -453,7 +688,7 @@ func (x *Dataset) String() string {
func (*Dataset) ProtoMessage() {}
func (x *Dataset) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[5]
mi := &file_manager_manager_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -466,21 +701,7 @@ func (x *Dataset) ProtoReflect() protoreflect.Message {
// Deprecated: Use Dataset.ProtoReflect.Descriptor instead.
func (*Dataset) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{5}
}
func (x *Dataset) GetProvider() string {
if x != nil {
return x.Provider
}
return ""
}
func (x *Dataset) GetId() string {
if x != nil {
return x.Id
}
return ""
return file_manager_manager_proto_rawDescGZIP(), []int{9}
}
func (x *Dataset) GetHash() []byte {
@@ -490,20 +711,26 @@ func (x *Dataset) GetHash() []byte {
return nil
}
func (x *Dataset) GetUserKey() []byte {
if x != nil {
return x.UserKey
}
return nil
}
type Algorithm struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
Hash []byte `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length.
Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length.
UserKey []byte `protobuf:"bytes,2,opt,name=userKey,proto3" json:"userKey,omitempty"`
}
func (x *Algorithm) Reset() {
*x = Algorithm{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[6]
mi := &file_manager_manager_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -516,7 +743,7 @@ func (x *Algorithm) String() string {
func (*Algorithm) ProtoMessage() {}
func (x *Algorithm) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[6]
mi := &file_manager_manager_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -529,21 +756,7 @@ func (x *Algorithm) ProtoReflect() protoreflect.Message {
// Deprecated: Use Algorithm.ProtoReflect.Descriptor instead.
func (*Algorithm) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{6}
}
func (x *Algorithm) GetProvider() string {
if x != nil {
return x.Provider
}
return ""
}
func (x *Algorithm) GetId() string {
if x != nil {
return x.Id
}
return ""
return file_manager_manager_proto_rawDescGZIP(), []int{10}
}
func (x *Algorithm) GetHash() []byte {
@@ -553,6 +766,13 @@ func (x *Algorithm) GetHash() []byte {
return nil
}
func (x *Algorithm) GetUserKey() []byte {
if x != nil {
return x.UserKey
}
return nil
}
type AgentConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -565,12 +785,13 @@ type AgentConfig struct {
ClientCaFile string `protobuf:"bytes,5,opt,name=client_ca_file,json=clientCaFile,proto3" json:"client_ca_file,omitempty"`
ServerCaFile string `protobuf:"bytes,6,opt,name=server_ca_file,json=serverCaFile,proto3" json:"server_ca_file,omitempty"`
LogLevel string `protobuf:"bytes,7,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"`
AttestedTls bool `protobuf:"varint,8,opt,name=attested_tls,json=attestedTls,proto3" json:"attested_tls,omitempty"`
}
func (x *AgentConfig) Reset() {
*x = AgentConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_manager_manager_proto_msgTypes[7]
mi := &file_manager_manager_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -583,7 +804,7 @@ func (x *AgentConfig) String() string {
func (*AgentConfig) ProtoMessage() {}
func (x *AgentConfig) ProtoReflect() protoreflect.Message {
mi := &file_manager_manager_proto_msgTypes[7]
mi := &file_manager_manager_proto_msgTypes[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -596,7 +817,7 @@ func (x *AgentConfig) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentConfig.ProtoReflect.Descriptor instead.
func (*AgentConfig) Descriptor() ([]byte, []int) {
return file_manager_manager_proto_rawDescGZIP(), []int{7}
return file_manager_manager_proto_rawDescGZIP(), []int{11}
}
func (x *AgentConfig) GetPort() string {
@@ -648,6 +869,13 @@ func (x *AgentConfig) GetLogLevel() string {
return ""
}
func (x *AgentConfig) GetAttestedTls() bool {
if x != nil {
return x.AttestedTls
}
return false
}
var File_manager_manager_proto protoreflect.FileDescriptor
var file_manager_manager_proto_rawDesc = []byte{
@@ -655,96 +883,120 @@ var file_manager_manager_proto_rawDesc = []byte{
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x22, 0x53, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12,
0x6f, 0x22, 0x25, 0x0a, 0x09, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x12, 0x18,
0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x38, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70,
0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x63,
0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x49, 0x64, 0x22, 0x53, 0x0a, 0x0b, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x50, 0x6f, 0x72, 0x74,
0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x0a, 0x41, 0x67, 0x65, 0x6e,
0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,
0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
0x12, 0x1e, 0x0a, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72,
0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x08, 0x41, 0x67, 0x65,
0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xde, 0x01, 0x0a, 0x0a, 0x41, 0x67, 0x65, 0x6e, 0x74,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74,
0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74,
0x54, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x25,
0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12,
0x1e, 0x0a, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x12,
0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x08, 0x41, 0x67, 0x65, 0x6e,
0x74, 0x4c, 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25,
0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x09, 0x74,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xbb, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a,
0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74,
0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x12,
0x36, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41,
0x67, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x67, 0x65,
0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x5f, 0x72,
0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00,
0x52, 0x06, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x22, 0x9f, 0x02, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x2c, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x10, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61,
0x73, 0x65, 0x74, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12, 0x32, 0x0a,
0x0a, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x6c, 0x67, 0x6f,
0x72, 0x69, 0x74, 0x68, 0x6d, 0x52, 0x0a, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d,
0x73, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73,
0x75, 0x6d, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x12, 0x37, 0x0a, 0x0c,
0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65,
0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x49, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74,
0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04,
0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68,
0x22, 0x4b, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x1a, 0x0a,
0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73,
0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xd6, 0x01,
0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a,
0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69,
0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x46, 0x69,
0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a,
0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x46,
0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x61,
0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72,
0x76, 0x65, 0x72, 0x43, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67,
0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f,
0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x32, 0x5b, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63,
0x65, 0x73, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x1a, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x70,
0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x00, 0x28,
0x01, 0x30, 0x01, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x38, 0x0a, 0x09,
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xbb, 0x01, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x30,
0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e,
0x74, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67,
0x12, 0x36, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
0x41, 0x67, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x5f,
0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x72, 0x2e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48,
0x00, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x22, 0xd6, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53,
0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x34, 0x0a, 0x06,
0x72, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x48, 0x00, 0x52, 0x06, 0x72, 0x75, 0x6e, 0x52,
0x65, 0x71, 0x12, 0x38, 0x0a, 0x0c, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52,
0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x72, 0x2e, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0c,
0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x44, 0x0a, 0x0f,
0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
0x53, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48,
0x00, 0x52, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xb6, 0x02,
0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e,
0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x08, 0x64, 0x61, 0x74,
0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6d, 0x61,
0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x52, 0x08, 0x64,
0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72,
0x69, 0x74, 0x68, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x61, 0x6e,
0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x52, 0x09,
0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x42, 0x0a, 0x10, 0x72, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65,
0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x52, 0x0f, 0x72, 0x65,
0x73, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x73, 0x12, 0x37, 0x0a,
0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x67,
0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x2a, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72,
0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b,
0x65, 0x79, 0x22, 0x37, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a,
0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73,
0x68, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0c, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x22, 0x39, 0x0a, 0x09, 0x41,
0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07,
0x75, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x75,
0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x0b, 0x41, 0x67, 0x65, 0x6e, 0x74,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f,
0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x1b,
0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x63, 0x65, 0x72, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6b,
0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b,
0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x24, 0x0a, 0x0e,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x63, 0x61, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x06,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x61, 0x46, 0x69,
0x6c, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18,
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12,
0x21, 0x0a, 0x0c, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6c, 0x73, 0x18,
0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x64, 0x54,
0x6c, 0x73, 0x32, 0x5d, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x4b, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x12,
0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e,
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74,
0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30,
0x01, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -759,34 +1011,42 @@ func file_manager_manager_proto_rawDescGZIP() []byte {
return file_manager_manager_proto_rawDescData
}
var file_manager_manager_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_manager_manager_proto_goTypes = []interface{}{
(*RunResponse)(nil), // 0: manager.RunResponse
(*AgentEvent)(nil), // 1: manager.AgentEvent
(*AgentLog)(nil), // 2: manager.AgentLog
(*ClientStreamMessage)(nil), // 3: manager.ClientStreamMessage
(*ComputationRunReq)(nil), // 4: manager.ComputationRunReq
(*Dataset)(nil), // 5: manager.Dataset
(*Algorithm)(nil), // 6: manager.Algorithm
(*AgentConfig)(nil), // 7: manager.AgentConfig
(*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp
var file_manager_manager_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_manager_manager_proto_goTypes = []any{
(*Terminate)(nil), // 0: manager.Terminate
(*StopComputation)(nil), // 1: manager.StopComputation
(*RunResponse)(nil), // 2: manager.RunResponse
(*AgentEvent)(nil), // 3: manager.AgentEvent
(*AgentLog)(nil), // 4: manager.AgentLog
(*ClientStreamMessage)(nil), // 5: manager.ClientStreamMessage
(*ServerStreamMessage)(nil), // 6: manager.ServerStreamMessage
(*ComputationRunReq)(nil), // 7: manager.ComputationRunReq
(*ResultConsumer)(nil), // 8: manager.ResultConsumer
(*Dataset)(nil), // 9: manager.Dataset
(*Algorithm)(nil), // 10: manager.Algorithm
(*AgentConfig)(nil), // 11: manager.AgentConfig
(*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
}
var file_manager_manager_proto_depIdxs = []int32{
8, // 0: manager.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp
8, // 1: manager.AgentLog.timestamp:type_name -> google.protobuf.Timestamp
2, // 2: manager.ClientStreamMessage.agent_log:type_name -> manager.AgentLog
1, // 3: manager.ClientStreamMessage.agent_event:type_name -> manager.AgentEvent
0, // 4: manager.ClientStreamMessage.run_res:type_name -> manager.RunResponse
5, // 5: manager.ComputationRunReq.datasets:type_name -> manager.Dataset
6, // 6: manager.ComputationRunReq.algorithms:type_name -> manager.Algorithm
7, // 7: manager.ComputationRunReq.agent_config:type_name -> manager.AgentConfig
3, // 8: manager.ManagerService.Process:input_type -> manager.ClientStreamMessage
4, // 9: manager.ManagerService.Process:output_type -> manager.ComputationRunReq
9, // [9:10] is the sub-list for method output_type
8, // [8:9] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
12, // 0: manager.AgentEvent.timestamp:type_name -> google.protobuf.Timestamp
12, // 1: manager.AgentLog.timestamp:type_name -> google.protobuf.Timestamp
4, // 2: manager.ClientStreamMessage.agent_log:type_name -> manager.AgentLog
3, // 3: manager.ClientStreamMessage.agent_event:type_name -> manager.AgentEvent
2, // 4: manager.ClientStreamMessage.run_res:type_name -> manager.RunResponse
7, // 5: manager.ServerStreamMessage.runReq:type_name -> manager.ComputationRunReq
0, // 6: manager.ServerStreamMessage.terminateReq:type_name -> manager.Terminate
1, // 7: manager.ServerStreamMessage.stopComputation:type_name -> manager.StopComputation
9, // 8: manager.ComputationRunReq.datasets:type_name -> manager.Dataset
10, // 9: manager.ComputationRunReq.algorithm:type_name -> manager.Algorithm
8, // 10: manager.ComputationRunReq.result_consumers:type_name -> manager.ResultConsumer
11, // 11: manager.ComputationRunReq.agent_config:type_name -> manager.AgentConfig
5, // 12: manager.ManagerService.Process:input_type -> manager.ClientStreamMessage
6, // 13: manager.ManagerService.Process:output_type -> manager.ServerStreamMessage
13, // [13:14] is the sub-list for method output_type
12, // [12:13] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
}
func init() { file_manager_manager_proto_init() }
@@ -795,7 +1055,31 @@ func file_manager_manager_proto_init() {
return
}
if !protoimpl.UnsafeEnabled {
file_manager_manager_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Terminate); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_manager_manager_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*StopComputation); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_manager_manager_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*RunResponse); i {
case 0:
return &v.state
@@ -807,7 +1091,7 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*AgentEvent); i {
case 0:
return &v.state
@@ -819,7 +1103,7 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*AgentLog); i {
case 0:
return &v.state
@@ -831,7 +1115,7 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*ClientStreamMessage); i {
case 0:
return &v.state
@@ -843,7 +1127,19 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*ServerStreamMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_manager_manager_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*ComputationRunReq); i {
case 0:
return &v.state
@@ -855,7 +1151,19 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[8].Exporter = func(v any, i int) any {
switch v := v.(*ResultConsumer); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_manager_manager_proto_msgTypes[9].Exporter = func(v any, i int) any {
switch v := v.(*Dataset); i {
case 0:
return &v.state
@@ -867,7 +1175,7 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[10].Exporter = func(v any, i int) any {
switch v := v.(*Algorithm); i {
case 0:
return &v.state
@@ -879,7 +1187,7 @@ func file_manager_manager_proto_init() {
return nil
}
}
file_manager_manager_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
file_manager_manager_proto_msgTypes[11].Exporter = func(v any, i int) any {
switch v := v.(*AgentConfig); i {
case 0:
return &v.state
@@ -892,18 +1200,23 @@ func file_manager_manager_proto_init() {
}
}
}
file_manager_manager_proto_msgTypes[3].OneofWrappers = []interface{}{
file_manager_manager_proto_msgTypes[5].OneofWrappers = []any{
(*ClientStreamMessage_AgentLog)(nil),
(*ClientStreamMessage_AgentEvent)(nil),
(*ClientStreamMessage_RunRes)(nil),
}
file_manager_manager_proto_msgTypes[6].OneofWrappers = []any{
(*ServerStreamMessage_RunReq)(nil),
(*ServerStreamMessage_TerminateReq)(nil),
(*ServerStreamMessage_StopComputation)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_manager_manager_proto_rawDesc,
NumEnums: 0,
NumMessages: 8,
NumMessages: 12,
NumExtensions: 0,
NumServices: 1,
},
+13 -12
View File
@@ -3,8 +3,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v4.25.3
// - protoc-gen-go-grpc v1.4.0
// - protoc v5.27.2
// source: manager/manager.proto
package manager
@@ -18,8 +18,8 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// Requires gRPC-Go v1.62.0 or later.
const _ = grpc.SupportPackageIsVersion8
const (
ManagerService_Process_FullMethodName = "/manager.ManagerService/Process"
@@ -41,17 +41,18 @@ func NewManagerServiceClient(cc grpc.ClientConnInterface) ManagerServiceClient {
}
func (c *managerServiceClient) Process(ctx context.Context, opts ...grpc.CallOption) (ManagerService_ProcessClient, error) {
stream, err := c.cc.NewStream(ctx, &ManagerService_ServiceDesc.Streams[0], ManagerService_Process_FullMethodName, opts...)
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ManagerService_ServiceDesc.Streams[0], ManagerService_Process_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &managerServiceProcessClient{stream}
x := &managerServiceProcessClient{ClientStream: stream}
return x, nil
}
type ManagerService_ProcessClient interface {
Send(*ClientStreamMessage) error
Recv() (*ComputationRunReq, error)
Recv() (*ServerStreamMessage, error)
grpc.ClientStream
}
@@ -63,8 +64,8 @@ func (x *managerServiceProcessClient) Send(m *ClientStreamMessage) error {
return x.ClientStream.SendMsg(m)
}
func (x *managerServiceProcessClient) Recv() (*ComputationRunReq, error) {
m := new(ComputationRunReq)
func (x *managerServiceProcessClient) Recv() (*ServerStreamMessage, error) {
m := new(ServerStreamMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
@@ -100,11 +101,11 @@ func RegisterManagerServiceServer(s grpc.ServiceRegistrar, srv ManagerServiceSer
}
func _ManagerService_Process_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(ManagerServiceServer).Process(&managerServiceProcessServer{stream})
return srv.(ManagerServiceServer).Process(&managerServiceProcessServer{ServerStream: stream})
}
type ManagerService_ProcessServer interface {
Send(*ComputationRunReq) error
Send(*ServerStreamMessage) error
Recv() (*ClientStreamMessage, error)
grpc.ServerStream
}
@@ -113,7 +114,7 @@ type managerServiceProcessServer struct {
grpc.ServerStream
}
func (x *managerServiceProcessServer) Send(m *ComputationRunReq) error {
func (x *managerServiceProcessServer) Send(m *ServerStreamMessage) error {
return x.ServerStream.SendMsg(m)
}
+237
View File
@@ -0,0 +1,237 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package progressbar
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"github.com/ultravioletrs/cocos/agent"
"golang.org/x/term"
)
const (
progressBarDots = "... "
leftBracket = "["
rightBracket = "]"
head = ">"
body = "="
bodyPadding = "."
bufferSize = 1024 * 1024
)
var (
_ streamSender = (*algoClientWrapper)(nil)
_ streamSender = (*dataClientWrapper)(nil)
)
type streamSender interface {
Send(interface{}) error
CloseAndRecv() (interface{}, error)
}
type algoClientWrapper struct {
client *agent.AgentService_AlgoClient
}
func (a *algoClientWrapper) Send(req interface{}) error {
algoReq, ok := req.(*agent.AlgoRequest)
if !ok {
return fmt.Errorf("expected *AlgoRequest, got %T", req)
}
return (*a.client).Send(algoReq)
}
func (a *algoClientWrapper) CloseAndRecv() (interface{}, error) {
return (*a.client).CloseAndRecv()
}
type dataClientWrapper struct {
client *agent.AgentService_DataClient
}
func (a *dataClientWrapper) Send(req interface{}) error {
dataReq, ok := req.(*agent.DataRequest)
if !ok {
return fmt.Errorf("expected *DataRequest, got %T", req)
}
return (*a.client).Send(dataReq)
}
func (a *dataClientWrapper) CloseAndRecv() (interface{}, error) {
return (*a.client).CloseAndRecv()
}
type ProgressBar struct {
numberOfBytes int
currentUploadedBytes int
currentUploadPercentage int
description string
maxWidth int
}
func New() *ProgressBar {
return &ProgressBar{}
}
func (p *ProgressBar) SendAlgorithm(description string, buffer *bytes.Buffer, stream *agent.AgentService_AlgoClient) error {
return p.sendData(description, buffer, &algoClientWrapper{client: stream}, func(data []byte) interface{} {
return &agent.AlgoRequest{Algorithm: data}
})
}
func (p *ProgressBar) SendData(description string, buffer *bytes.Buffer, stream *agent.AgentService_DataClient) error {
return p.sendData(description, buffer, &dataClientWrapper{client: stream}, func(data []byte) interface{} {
return &agent.DataRequest{Dataset: data}
})
}
func (p *ProgressBar) sendData(description string, buffer *bytes.Buffer, stream streamSender, createRequest func([]byte) interface{}) error {
p.reset(description, buffer.Len())
buf := make([]byte, bufferSize)
for {
n, err := buffer.Read(buf)
if err == io.EOF {
if _, err := io.WriteString(os.Stdout, "\n"); err != nil {
return err
}
break
}
if err != nil {
return err
}
p.updateProgress(n)
if err := stream.Send(createRequest(buf[:n])); err != nil {
return err
}
if err := p.renderProgressBar(); err != nil {
return err
}
}
_, err := stream.CloseAndRecv()
return err
}
func (p *ProgressBar) reset(description string, totalBytes int) {
p.currentUploadedBytes = 0
p.currentUploadPercentage = 0
p.numberOfBytes = totalBytes
p.description = description
}
func (p *ProgressBar) updateProgress(bytesRead int) {
if p.currentUploadedBytes < p.numberOfBytes {
p.currentUploadedBytes += bytesRead
p.currentUploadPercentage = p.currentUploadedBytes * 100 / p.numberOfBytes
}
}
// Progress bar example: Uploading algorithm... 25% [==> ].
func (p *ProgressBar) renderProgressBar() error {
var builder strings.Builder
// Get terminal width.
width, err := terminalWidth()
if err != nil {
return err
}
if p.maxWidth < width {
p.maxWidth = width
}
if err := p.clearProgressBar(); err != nil {
return err
}
// The progress bar starts with the description.
if _, err := builder.WriteString(p.description); err != nil {
return err
}
// Add dots to progress bar.
if _, err := builder.WriteString(progressBarDots); err != nil {
return err
}
// Add uploaded percentage.
strCurrentUploadPercentage := fmt.Sprintf("%4d%% ", p.currentUploadPercentage)
if _, err := builder.WriteString(strCurrentUploadPercentage); err != nil {
return err
}
// Add letf bracket and space to progress bar.
if _, err := builder.WriteString(leftBracket); err != nil {
return err
}
progressWidth := width - builder.Len() - len(rightBracket+" ")
numOfCharactersBody := progressWidth * p.currentUploadPercentage / 100
if numOfCharactersBody == 0 {
numOfCharactersBody = 1
}
numOfCharactersPadding := progressWidth - numOfCharactersBody
// Add body which represents the percentage.
progress := strings.Repeat(body, numOfCharactersBody-1)
// Add progress to the progress bar.
if _, err := builder.WriteString(progress); err != nil {
return err
}
// Add head to progress bar.
if _, err := builder.WriteString(head); err != nil {
return err
}
// Add padding to end of bar.
padding := strings.Repeat(bodyPadding, numOfCharactersPadding)
// Add padding to progress bar.
if _, err := builder.WriteString(padding); err != nil {
return err
}
// Add right bracket to progress bar.
if _, err := builder.WriteString(rightBracket); err != nil {
return err
}
// Write progress bar.
if _, err := io.WriteString(os.Stdout, builder.String()); err != nil {
return err
}
return nil
}
func terminalWidth() (int, error) {
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err == nil {
return width, nil
}
return 0, err
}
func (p *ProgressBar) clearProgressBar() error {
emptySpace := fmt.Sprintf("\r%s\r", strings.Repeat(" ", p.maxWidth))
if _, err := io.WriteString(os.Stdout, emptySpace); err != nil {
return err
}
return nil
}
+101 -24
View File
@@ -3,63 +3,107 @@
package sdk
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"errors"
"log/slog"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/pkg/progressbar"
"google.golang.org/grpc/metadata"
)
var _ agent.Service = (*agentSDK)(nil)
type SDK interface {
Algo(ctx context.Context, algorithm agent.Algorithm, privKey any) error
Data(ctx context.Context, dataset agent.Dataset, privKey any) error
Result(ctx context.Context, privKey any) ([]byte, error)
Attestation(ctx context.Context, reportData [size64]byte) ([]byte, error)
}
const size64 = 64
const (
size64 = 64
algoProgressBarDescription = "Uploading algorithm"
dataProgressBarDescription = "Uploading data"
)
type agentSDK struct {
client agent.AgentServiceClient
logger *slog.Logger
}
func NewAgentSDK(log *slog.Logger, agentClient agent.AgentServiceClient) *agentSDK {
func NewAgentSDK(log *slog.Logger, agentClient agent.AgentServiceClient) SDK {
return &agentSDK{
client: agentClient,
logger: log,
}
}
func (sdk *agentSDK) Algo(ctx context.Context, algorithm agent.Algorithm) error {
request := &agent.AlgoRequest{
Algorithm: algorithm.Algorithm,
Provider: algorithm.Provider,
Id: algorithm.ID,
func (sdk *agentSDK) Algo(ctx context.Context, algorithm agent.Algorithm, privKey any) error {
md, err := generateMetadata(string(auth.AlgorithmProviderRole), privKey)
if err != nil {
sdk.logger.Error("Failed to generate metadata")
return err
}
if _, err := sdk.client.Algo(ctx, request); err != nil {
ctx = metadata.NewOutgoingContext(ctx, md)
stream, err := sdk.client.Algo(ctx)
if err != nil {
sdk.logger.Error("Failed to call Algo RPC")
return err
}
algoBuffer := bytes.NewBuffer(algorithm.Algorithm)
return nil
}
func (sdk *agentSDK) Data(ctx context.Context, dataset agent.Dataset) error {
request := &agent.DataRequest{
Dataset: dataset.Dataset,
Provider: dataset.Provider,
Id: dataset.ID,
}
if _, err := sdk.client.Data(ctx, request); err != nil {
sdk.logger.Error("Failed to call Data RPC")
pb := progressbar.New()
if err := pb.SendAlgorithm(algoProgressBarDescription, algoBuffer, &stream); err != nil {
sdk.logger.Error("Failed to send Algorithm")
return err
}
return nil
}
func (sdk *agentSDK) Result(ctx context.Context, consumer string) ([]byte, error) {
request := &agent.ResultRequest{
Consumer: consumer,
func (sdk *agentSDK) Data(ctx context.Context, dataset agent.Dataset, privKey any) error {
md, err := generateMetadata(string(auth.DataProviderRole), privKey)
if err != nil {
sdk.logger.Error("Failed to generate metadata")
return err
}
ctx = metadata.NewOutgoingContext(ctx, md)
stream, err := sdk.client.Data(ctx)
if err != nil {
sdk.logger.Error("Failed to call Data RPC")
return err
}
dataBuffer := bytes.NewBuffer(dataset.Dataset)
pb := progressbar.New()
if err := pb.SendData(dataProgressBarDescription, dataBuffer, &stream); err != nil {
sdk.logger.Error("Failed to send Data")
return err
}
return nil
}
func (sdk *agentSDK) Result(ctx context.Context, privKey any) ([]byte, error) {
request := &agent.ResultRequest{}
md, err := generateMetadata(string(auth.ConsumerRole), privKey)
if err != nil {
sdk.logger.Error("Failed to generate metadata")
return nil, err
}
ctx = metadata.NewOutgoingContext(ctx, md)
response, err := sdk.client.Result(ctx, request)
if err != nil {
sdk.logger.Error("Failed to call Result RPC")
@@ -82,3 +126,36 @@ func (sdk *agentSDK) Attestation(ctx context.Context, reportData [size64]byte) (
return response.File, nil
}
func signData(userID string, privKey crypto.Signer) ([]byte, error) {
var signature []byte
var err error
switch k := privKey.(type) {
case ed25519.PrivateKey:
signature, err = k.Sign(rand.Reader, []byte(userID), crypto.Hash(0))
case *rsa.PrivateKey, *ecdsa.PrivateKey:
hash := sha256.Sum256([]byte(userID))
signature, err = privKey.Sign(rand.Reader, hash[:], crypto.SHA256)
default:
return nil, errors.New("unsupported key type")
}
if err != nil {
return nil, err
}
return signature, nil
}
func generateMetadata(userID string, privateKey crypto.PrivateKey) (metadata.MD, error) {
signature, err := signData(userID, privateKey.(crypto.Signer))
if err != nil {
return nil, err
}
kv := make(map[string]string)
kv[auth.UserMetadataKey] = userID
kv[auth.SignatureMetadataKey] = base64.StdEncoding.EncodeToString(signature)
return metadata.New(kv), nil
}
+435
View File
@@ -0,0 +1,435 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"errors"
"os"
"testing"
mglog "github.com/absmach/magistrala/logger"
"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/sdk"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
var (
algoPath = "../../test/manual/algo/lin_reg.py"
dataPath = "../../test/manual/data/iris.csv"
errInappropriateIoctl = errors.New("inappropriate ioctl for device")
)
func TestAlgo(t *testing.T) {
logger, err := mglog.New(os.Stdout, "info")
require.NoError(t, err)
conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := agent.NewAgentServiceClient(conn)
sdk := sdk.NewAgentSDK(logger, client)
algo, err := os.ReadFile(algoPath)
require.NoError(t, err)
algoHash := sha3.Sum256(algo)
algorithmProviderKey, algorithmProviderPubKey := generateKeys(t, "ed25519")
algoProvider1Key, algoProvider1PubKey := generateKeys(t, "ed25519")
algorithm := agent.Algorithm{
Algorithm: algo,
Hash: algoHash,
UserKey: algorithmProviderPubKey,
}
cases := []struct {
name string
err error
algo agent.Algorithm
userKey any
}{
{
name: "Test Algo successfully",
algo: algorithm,
userKey: algorithmProviderKey,
err: nil,
},
{
name: "hash mismatch",
algo: agent.Algorithm{
Algorithm: algo,
Hash: sha3.Sum256([]byte("algo")),
UserKey: algoProvider1PubKey,
},
userKey: algoProvider1Key,
err: errInappropriateIoctl,
},
{
name: "no manifest expected",
algo: agent.Algorithm{
Algorithm: algo,
UserKey: algorithmProviderPubKey,
Hash: algoHash,
},
userKey: algorithmProviderKey,
err: errInappropriateIoctl,
},
{
name: "state not ready",
algo: agent.Algorithm{
Algorithm: algo,
UserKey: algorithmProviderPubKey,
Hash: algoHash,
},
userKey: algorithmProviderKey,
err: errInappropriateIoctl,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
svcCall := svc.On("Algo", mock.Anything, mock.Anything).Return(tc.err)
err = sdk.Algo(context.Background(), tc.algo, tc.userKey)
st, _ := status.FromError(err)
if tc.err != nil {
if st.Message() != tc.err.Error() {
t.Errorf("%s : Expected error message %q, but got %q", tc.name, tc.err.Error(), st.Message())
}
}
svcCall.Unset()
})
}
}
func TestData(t *testing.T) {
logger, err := mglog.New(os.Stdout, "info")
require.NoError(t, err)
conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := agent.NewAgentServiceClient(conn)
sdk := sdk.NewAgentSDK(logger, client)
data, err := os.ReadFile(dataPath)
require.NoError(t, err)
dataHash := sha3.Sum256(data)
dataProviderKey, dataProviderPubKey := generateKeys(t, "ecdsa")
dataProvider1Key, dataProvider1PubKey := generateKeys(t, "ed25519")
dataset := agent.Dataset{
Hash: dataHash,
Dataset: data,
UserKey: dataProviderPubKey,
}
cases := []struct {
name string
data agent.Dataset
userKey any
svcErr error
}{
{
name: "Test data successfully",
data: dataset,
userKey: dataProviderKey,
},
{
name: "undeclared dataset",
data: agent.Dataset{
Dataset: data,
UserKey: dataProvider1PubKey,
Hash: dataHash,
},
userKey: dataProvider1Key,
svcErr: errInappropriateIoctl,
},
{
name: "hash mismatch",
data: agent.Dataset{
Dataset: data,
UserKey: dataProvider1PubKey,
Hash: dataHash,
},
userKey: dataProvider1Key,
svcErr: errInappropriateIoctl,
},
{
name: "all manifest items received",
data: agent.Dataset{
Dataset: data,
UserKey: dataProvider1PubKey,
Hash: dataHash,
},
userKey: dataProvider1Key,
svcErr: errInappropriateIoctl,
},
{
name: "missing dataset file",
data: agent.Dataset{
UserKey: dataProvider1PubKey,
Hash: dataHash,
},
userKey: dataProvider1Key,
svcErr: errors.New("dataset CSV file is required"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
dataCall := svc.On("Data", mock.Anything, mock.Anything).Return(tc.svcErr)
err = sdk.Data(context.Background(), tc.data, tc.userKey)
st, _ := status.FromError(err)
if tc.svcErr != nil {
if st.Message() != tc.svcErr.Error() {
t.Errorf("%s: Expected error message %q, but got %q", tc.name, tc.svcErr.Error(), st.Message())
}
}
dataCall.Unset()
})
}
}
func TestResult(t *testing.T) {
logger, err := mglog.New(os.Stdout, "info")
require.NoError(t, err)
conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := agent.NewAgentServiceClient(conn)
sdk := sdk.NewAgentSDK(logger, client)
resultConsumerKey, _ := generateKeys(t, "ecdsa")
resultConsumer1Key, _ := generateKeys(t, "ed25519")
response := &agent.ResultResponse{
File: []byte{
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
},
}
cases := []struct {
name string
userKey any
response *agent.ResultResponse
svcRes []byte
err error
}{
{
name: "Test result successfully",
userKey: resultConsumerKey,
response: response,
svcRes: response.File,
err: nil,
},
{
name: "Test result successfully with ed25519 key type",
userKey: resultConsumer1Key,
response: response,
svcRes: response.File,
err: nil,
},
{
name: "Results not ready",
userKey: resultConsumer1Key,
response: &agent.ResultResponse{
File: []byte(nil),
},
svcRes: nil,
err: agent.ErrResultsNotReady,
},
{
name: "All manifest items received",
userKey: resultConsumer1Key,
response: &agent.ResultResponse{
File: []byte(nil),
},
svcRes: nil,
err: agent.ErrAllManifestItemsReceived,
},
{
name: "Undeclared consumer",
userKey: resultConsumer1Key,
response: &agent.ResultResponse{
File: []byte(nil),
},
svcRes: nil,
err: agent.ErrUndeclaredConsumer,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
svcCall := svc.On("Result", mock.Anything, mock.Anything).Return(tc.svcRes, tc.err)
res, err := sdk.Result(context.Background(), tc.userKey)
st, ok := status.FromError(err)
if !ok {
t.Fatalf("Expected gRPC status error, but got: %v", err)
}
if tc.err != nil {
if st.Message() != tc.err.Error() {
t.Errorf("%s: Expected error message %q, but got %q", tc.name, tc.err.Error(), st.Message())
}
}
assert.Equal(t, tc.response.File, res, tc.name)
svcCall.Unset()
})
}
}
func TestAttestation(t *testing.T) {
logger, err := mglog.New(os.Stdout, "info")
require.NoError(t, err)
resultConsumerKey, _ := generateKeys(t, "rsa")
resultConsumer1Key, _ := generateKeys(t, "ed25519")
reportData := make([]byte, 64)
report := []byte{
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
}
conn, err := grpc.DialContext(context.Background(), "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer conn.Close()
client := agent.NewAgentServiceClient(conn)
sdk := sdk.NewAgentSDK(logger, client)
_, err = rand.Read(reportData)
require.NoError(t, err)
cases := []struct {
name string
userKey any
reportData [agent.ReportDataSize]byte
response *agent.AttestationResponse
svcRes []byte
err error
}{
{
name: "fetch attestation report successfully",
userKey: resultConsumerKey,
reportData: [agent.ReportDataSize]byte(reportData),
response: &agent.AttestationResponse{
File: report,
},
svcRes: report,
err: nil,
},
{
name: "fetch attestation report with different key type",
userKey: resultConsumer1Key,
reportData: [agent.ReportDataSize]byte(reportData),
response: &agent.AttestationResponse{
File: report,
},
svcRes: report,
err: nil,
},
{
name: "failed to fetch attestation report",
userKey: resultConsumerKey,
reportData: [agent.ReportDataSize]byte(reportData),
response: &agent.AttestationResponse{
File: nil,
},
err: nil,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
svcCall := svc.On("Attestation", mock.Anything, mock.Anything).Return(tc.svcRes, tc.err)
res, err := sdk.Attestation(context.Background(), tc.reportData)
st, ok := status.FromError(err)
if !ok {
t.Fatalf("Expected gRPC status error, but got: %v", err)
}
if tc.err != nil {
if st.Message() != tc.err.Error() {
t.Errorf("%s: Expected error message %q, but got %q", tc.name, tc.err.Error(), st.Message())
}
}
assert.Equal(t, tc.response.File, res, tc.name)
svcCall.Unset()
})
}
}
func generateKeys(t *testing.T, keyType string) (priv any, pub []byte) {
switch keyType {
case "ecdsa":
privEcdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privEcdsaKey.PublicKey)
require.NoError(t, err)
return privEcdsaKey, pubKeyBytes
case "ed25519":
pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
pubKey, err := x509.MarshalPKIXPublicKey(pubEd25519Key)
require.NoError(t, err)
return privEd25519Key, pubKey
default:
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
require.NoError(t, err)
return privKey, pubKeyBytes
}
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package sdk_test
import (
"context"
"fmt"
"net"
"os"
"testing"
"github.com/ultravioletrs/cocos/agent"
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/mocks"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
const bufSize = 1024 * 1024
var (
lis *bufconn.Listener
svc = &mocks.Service{}
)
func TestMain(m *testing.M) {
lis = bufconn.Listen(bufSize)
s := grpc.NewServer()
agent.RegisterAgentServiceServer(s, agentgrpc.NewServer(svc))
go func() {
if err := s.Serve(lis); err != nil {
fmt.Println("Server exited with error:", err)
}
}()
code := m.Run()
s.Stop()
lis.Close()
os.Exit(code)
}
func bufDialer(context.Context, string) (net.Conn, error) {
return lis.Dial()
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "backend_info"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sev = "3.1.1"
sysinfo = "0.30.12"
regex = "1.10.4"
+14
View File
@@ -0,0 +1,14 @@
CARGO = cargo
TARGET = target
BUILD_DIR = $(TARGET)/release
BIN_NAME = backend_info
all: build
build:
$(CARGO) build --release
clean:
$(CARGO) clean
.PHONY: all build clean
+19
View File
@@ -0,0 +1,19 @@
# Rust project for fetching backend info
This rust project fetches information from the host system needed for validation of the attestation report. It outputs a JSON file that contains the said information.
The JSON file is in a format that can be used with the [go-sev-guest](https://github.com/google/go-sev-guest) library.
## Usage
Clone `cocos` repository:
```bash
git clone git@github.com:ultravioletrs/cocos.git
cd ./cocos/scripts/backend_info
make
```
Then run the binary. Keep in mind that you have to specify the policy of the Guest VM:
```bash
cd ./target/releas
# Run with option --policy (policy is 64 bit number)
./backend_info --policy 196608
```
+137
View File
@@ -0,0 +1,137 @@
use clap::{Arg, Command, value_parser};
use serde::Serialize;
use std::fs::File;
use std::io::Write;
use sysinfo::System;
use regex::Regex;
use sev::firmware::host::*;
const BACKEND_INFO_JSON : &str = "backend_info.json";
#[derive(Serialize)]
struct Vmpl {
value : u32,
}
#[derive(Serialize)]
struct SnpPolicy {
policy: u64,
family_id: Vec<u8>,
image_id: Vec<u8>,
vmpl: Vmpl,
minimum_tcb: u64,
minimum_launch_tcb: u64,
require_author_key: bool,
measurement: Vec<u8>,
host_data: Vec<u8>,
report_id_ma: Vec<u8>,
chip_id: Vec<u8>,
minimum_build: u32,
minimum_version: String,
permit_provisional_firmware: bool,
require_id_block: bool,
}
#[derive(Serialize)]
struct RootOfTrust {
product: String,
check_crl : bool,
disallow_network : bool,
}
#[derive(Serialize)]
struct Computation {
snp_policy: SnpPolicy,
root_of_trust: RootOfTrust,
}
fn get_product_name() -> String {
let mut sys = System::new_all();
sys.refresh_all();
let re = Regex::new(r"EPYC.*7..3.*").unwrap();
for cpu in sys.cpus() {
if re.is_match(cpu.brand()) {
return "Milan".to_string()
}
}
"Unknown".to_string()
}
fn get_uint64_from_tcb(tcb_version : &TcbVersion) -> u64 {
let microcode = (tcb_version.microcode as u64) << 56;
let snp = (tcb_version.snp as u64) << 48;
let tee = (tcb_version.tee as u64) << 8;
let bootloader = tcb_version.bootloader as u64;
microcode | snp | tee | bootloader
}
fn main() {
let matches = Command::new("Backend info")
.about("Processes command line options and outputs a JSON file for Attestation verification")
.arg(Arg::new("policy")
.long("policy")
.value_name("INT")
.help("Sets the policy integer")
.required(true)
.value_parser(value_parser!(u64)))
.get_matches();
let mut firmware: Firmware = Firmware::open().unwrap();
let status: SnpPlatformStatus = firmware.snp_platform_status().unwrap();
let policy: u64 = *matches.get_one::<u64>("policy").unwrap();
let family_id = vec![0];
let image_id = vec![0];
let vmpl = Vmpl { value: 0};
let minimum_tcb = get_uint64_from_tcb(&status.platform_tcb_version);
let minimum_launch_tcb = get_uint64_from_tcb(&status.platform_tcb_version);
let require_author_key = false;
let measurement = vec![0];
let host_data = vec![0];
let report_id_ma = vec![0];
let cpu_id: Identifier = firmware.get_identifier().unwrap();
let chip_id: Vec<u8> = cpu_id.0;
let minimum_build = status.build_id;
let minimum_version = status.version.to_string();
let permit_provisional_firmware = false;
let require_id_block = false;
let snp_policy = SnpPolicy {
policy,
family_id,
image_id,
vmpl,
minimum_tcb,
minimum_launch_tcb,
require_author_key,
measurement,
host_data,
report_id_ma,
chip_id,
minimum_build,
minimum_version,
permit_provisional_firmware,
require_id_block,
};
let root_of_trust = RootOfTrust {
product : get_product_name(),
check_crl : true,
disallow_network : false,
};
let computation = Computation {
snp_policy,
root_of_trust,
};
let json = serde_json::to_string_pretty(&computation).expect("Failed to serialize to JSON");
let mut file = File::create(BACKEND_INFO_JSON).expect("Failed to create file");
file.write_all(json.as_bytes()).expect("Failed to write to file");
println!("Computation JSON has been written to {}", BACKEND_INFO_JSON);
}
+42 -19
View File
@@ -4,13 +4,15 @@ package main
import (
"context"
"encoding/pem"
"fmt"
"log"
"log/slog"
"os"
"strconv"
mglog "github.com/absmach/magistrala/logger"
"github.com/ultravioletrs/cocos/internal/env"
"github.com/caarlos0/env/v11"
"github.com/ultravioletrs/cocos/internal/server"
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
managergrpc "github.com/ultravioletrs/cocos/manager/api/grpc"
@@ -18,6 +20,7 @@ import (
"golang.org/x/crypto/sha3"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
)
@@ -29,15 +32,17 @@ const (
)
var (
algoPath = "./test/manual/algo/lin_reg.py"
dataPath = "./test/manual/data/iris.csv"
algoPath = "./test/manual/algo/lin_reg.py"
dataPath = "./test/manual/data/iris.csv"
attestedTLS = false
pubKeyFile string
)
type svc struct {
logger *slog.Logger
}
func (s *svc) Run(ipAdress string, reqChan chan *manager.ComputationRunReq) {
func (s *svc) Run(ipAdress string, reqChan chan *manager.ServerStreamMessage, auth credentials.AuthInfo) {
s.logger.Debug(fmt.Sprintf("received who am on ip address %s", ipAdress))
algo, err := os.ReadFile(algoPath)
if err != nil {
@@ -49,28 +54,46 @@ func (s *svc) Run(ipAdress string, reqChan chan *manager.ComputationRunReq) {
s.logger.Error(fmt.Sprintf("failed to read data file: %s", err))
return
}
pubKey, err := os.ReadFile(pubKeyFile)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to read public key file: %s", err))
return
}
pubPem, _ := pem.Decode(pubKey)
algoHash := sha3.Sum256(algo)
dataHash := sha3.Sum256(data)
reqChan <- &manager.ComputationRunReq{
Id: "1",
Name: "sample computation",
Description: "sample descrption",
Datasets: []*manager.Dataset{{Id: "1", Provider: "provider1", Hash: dataHash[:]}},
Algorithms: []*manager.Algorithm{{Id: "1", Provider: "provider1", Hash: algoHash[:]}},
ResultConsumers: []string{"consumer1"},
AgentConfig: &manager.AgentConfig{
Port: "7002",
LogLevel: "debug",
reqChan <- &manager.ServerStreamMessage{
Message: &manager.ServerStreamMessage_RunReq{
RunReq: &manager.ComputationRunReq{
Id: "1",
Name: "sample computation",
Description: "sample descrption",
Datasets: []*manager.Dataset{{Hash: dataHash[:], UserKey: pubPem.Bytes}},
Algorithm: &manager.Algorithm{Hash: algoHash[:], UserKey: pubPem.Bytes},
ResultConsumers: []*manager.ResultConsumer{{UserKey: pubPem.Bytes}},
AgentConfig: &manager.AgentConfig{
Port: "7002",
LogLevel: "debug",
AttestedTls: attestedTLS,
},
},
},
}
}
func main() {
if len(os.Args) < 3 {
log.Fatalf("usage: %s <data-path> <algo-path>", os.Args[0])
if len(os.Args) < 5 {
log.Fatalf("usage: %s <data-path> <algo-path> <public-key-path> <attested-tls-bool>", os.Args[0])
}
dataPath = os.Args[1]
algoPath = os.Args[2]
pubKeyFile = os.Args[3]
attestedTLSParam, err := strconv.ParseBool(os.Args[4])
if err != nil {
log.Fatalf("usage: %s <data-path> <algo-path> <attested-tls-bool>, <attested-tls-bool> must be a bool value", os.Args[0])
}
attestedTLS = attestedTLSParam
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
incomingChan := make(chan *manager.ClientStreamMessage)
@@ -96,15 +119,15 @@ func main() {
registerAgentServiceServer := func(srv *grpc.Server) {
reflection.Register(srv)
manager.RegisterManagerServiceServer(srv, managergrpc.NewServer(ctx, incomingChan, &svc{logger: logger}))
manager.RegisterManagerServiceServer(srv, managergrpc.NewServer(incomingChan, &svc{logger: logger}))
}
grpcServerConfig := server.Config{Port: defaultPort}
if err := env.Parse(&grpcServerConfig, env.Options{}); err != nil {
if err := env.ParseWithOptions(&grpcServerConfig, env.Options{}); err != nil {
logger.Error(fmt.Sprintf("failed to load %s gRPC client configuration : %s", svcName, err))
return
}
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger)
gs := grpcserver.New(ctx, cancel, svcName, grpcServerConfig, registerAgentServiceServer, logger, nil, nil)
g.Go(func() error {
return gs.Start()
+56 -16
View File
@@ -2,27 +2,67 @@
## CLI
Throughout the tests, we assume that our current working directory is the root of the `agent` repository, both on the host machine and in the VM.
Throughout the tests, we assume that our current working directory is the root of the `cocos` repository, both on the host machine and in the VM.
### Python requirements
### Algorithm requirements
Do this both on the host machine and in the VM.
```sh
apt update
apt install python3-pip
pip3 install pandas scikit-learn
Agent accepts the algorithm as a binary that take in two command line arguments.
```shell
algorithm-file <unix socket path> <dataset file paths>
```
The algorithm program should return the results to a socket and an example can be seen in this [file](./algo/lin_reg.py).
### Agent-CLI interaction
Agent is started automatically in the VM when launched but requires configuration and manifest to be passed by manager. Alternatively you can pass configuration using this [simplified script](./agent-config/main.go)
Open console on the host, and run
For attested TLS, you will have to calculate the VM's measurement, which can be done using a tool [sev-snp-measure](https://pypi.org/project/sev-snp-measure/).
```sh
```bash
# Define the path to the OVMF, KERNEL, INITRD and CMD Kernel line arguments.
OVMF_CODE="/home/cocosai/ovmf/Build/AmdSev/DEBUG_GCC5/FV/OVMF.fd"
INITRD="/home/cocosai/initramfs.cpio.gz"
KERNEL="/home/cocosai/bzImage"
LINE="earlyprintk=serial console=ttyS0"
# Call sev-snp-measure
sev-snp-measure --mode snp --vcpus 4 --vcpu-type EPYC-v4 --ovmf $OVMF_CODE --kernel $KERNEL --initrd $INITRD --append "$LINE" --output-format base64
```
To speed up the verification process of attested TLS, download the ARK and ASK certificates using the CLI tool. The CLI tool will download the certificates under your home directory in the `.cocos` directory.
```bash
go run cmd/cli/main.go ca-bundle <path/to/platfrom_info.json>
```
In the following text, we can see an example of how the CLI tool is used.
```bash
export AGENT_GRPC_URL=localhost:7002
export MANAGER_GRPC_URL=localhost:7001
# For attested TLS, the CLI needs a file containing the necessary information
# about the SEV-SNP capable backend. This information will be used to verify
# the attestation report received from the agent.
# The backend_info.json file can be generated using Rust by running:
cd scripts/backend_info
make
sudo ./target/release/backend_info --policy 196608 # Default value of the policy should be 196608
# The output file backend_info.json will be generated in the directory from which the executable has been called.
cd ../..
# The CLI should also be aware of the VM measurement. To add the measurement
# to the .json file that contains the information about the platform, run CLI
# with the measurement in base64 format and the path of the backend_info.json file.:
go run cmd/cli/main.go backend measurement '<measurement>' '<backend_info.json>'
# If the VM is booted with the QEMU host data option, the CLI should also know
# the host data information. To add the host data to the .json file that contains
# the information about the platform, run CLI with the host data in base64 format
# and the path of the backend_info.json file.:
go run cmd/cli/main.go backend measurement '<host-data>' '<backend_info.json>'
# For attested TLS, also define the path to the backend_info.json that contains reference values for the fields of the attestation report
export AGENT_GRPC_MANIFEST=./scripts/backend_info/backend_info.json
export AGENT_GRPC_ATTESTED_TLS=true
# Retieve Attestation
go run cmd/cli/main.go attestation get '<report_data>'
@@ -31,16 +71,16 @@ go run cmd/cli/main.go attestation get '<report_data>'
go run cmd/cli/main.go attestation validate '<attesation>' --report_data '<report_data>'
# Run the CLI program with algorithm input
go run cmd/cli/main.go algo test/manual/algo/lin_reg.py Algorithm1 AlgorithmProvider1
# 2023/09/21 10:43:53 Uploading algorithm binary: test/manual/algo/lin_reg.py
go run cmd/cli/main.go algo test/manual/algo/lin_reg.bin <private_key_file_path>
# 2023/09/21 10:43:53 Uploading algorithm binary: test/manual/algo/lin_reg.bin
# Run the CLI program with dataset input
go run cmd/cli/main.go data test/manual/data/iris.csv Dataset1 Provider1
go run cmd/cli/main.go data test/manual/data/iris.csv Dataset2 Provider2
go run cmd/cli/main.go data test/manual/data/iris.csv <private_key_file_path>
go run cmd/cli/main.go data test/manual/data/iris.csv <private_key_file_path>
# 2023/09/21 10:45:25 Uploading dataset CSV: test/manual/data/iris.csv
# Run the CLI program to fetch computation result
go run cmd/cli/main.go result Consumer1
go run cmd/cli/main.go result <private_key_file_path>
# 2023/09/21 10:45:39 Retrieving computation result file
# 2023/09/21 10:45:40 Computation result retrieved and saved successfully!
```
+53 -65
View File
@@ -7,80 +7,63 @@ package main
import (
"encoding/json"
"encoding/pem"
"fmt"
"log"
"os"
"strconv"
"github.com/mdlayher/vsock"
"github.com/ultravioletrs/cocos/pkg/manager"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/manager"
"github.com/ultravioletrs/cocos/manager/qemu"
pkgmanager "github.com/ultravioletrs/cocos/pkg/manager"
"golang.org/x/crypto/sha3"
"google.golang.org/protobuf/proto"
)
const VsockConfigPort uint32 = 9999
type AgentConfig struct {
LogLevel string `json:"log_level"`
InstanceID string `json:"instance_id"`
Host string `json:"host"`
Port string `json:"port"`
CertFile string `json:"cert_file"`
KeyFile string `json:"server_key"`
}
type Computation struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Datasets Datasets `json:"datasets,omitempty"`
Algorithms Algorithms `json:"algorithms,omitempty"`
ResultConsumers []string `json:"result_consumers,omitempty"`
AgentConfig AgentConfig `json:"agent_config,omitempty"`
}
func (d *Datasets) String() string {
dat, err := json.Marshal(d)
if err != nil {
return ""
}
return string(dat)
}
func (a *Algorithms) String() string {
dat, err := json.Marshal(a)
if err != nil {
return ""
}
return string(dat)
}
type Dataset struct {
Dataset []byte `json:"-"`
Provider string `json:"provider,omitempty"`
ID string `json:"id,omitempty"`
}
type Datasets []Dataset
type Algorithm struct {
Algorithm []byte `json:"-"`
Provider string `json:"provider,omitempty"`
ID string `json:"id,omitempty"`
}
type Algorithms []Algorithm
func main() {
l, err := vsock.Listen(9997, nil)
if len(os.Args) < 5 {
log.Fatalf("usage: %s <data-path> <algo-path> <public-key-path> <attested-tls-bool>", os.Args[0])
}
dataPath := os.Args[1]
algoPath := os.Args[2]
pubKeyFile := os.Args[3]
attestedTLSParam, err := strconv.ParseBool(os.Args[4])
if err != nil {
log.Fatalf("usage: %s <data-path> <algo-path> <attested-tls-bool>, <attested-tls-bool> must be a bool value", os.Args[0])
}
attestedTLS := attestedTLSParam
algo, err := os.ReadFile(algoPath)
if err != nil {
log.Fatalf(fmt.Sprintf("failed to read algorithm file: %s", err))
}
data, err := os.ReadFile(dataPath)
if err != nil {
log.Fatalf(fmt.Sprintf("failed to read data file: %s", err))
}
pubKey, err := os.ReadFile(pubKeyFile)
if err != nil {
log.Fatalf(fmt.Sprintf("failed to read public key file: %s", err))
}
pubPem, _ := pem.Decode(pubKey)
algoHash := sha3.Sum256(algo)
dataHash := sha3.Sum256(data)
l, err := vsock.Listen(manager.ManagerVsockPort, nil)
if err != nil {
log.Fatal(err)
}
ac := Computation{
ac := agent.Computation{
ID: "123",
Datasets: Datasets{Dataset{ID: "1", Provider: "pr1"}},
Algorithms: Algorithms{Algorithm{ID: "1", Provider: "pr1"}},
ResultConsumers: []string{"1"},
AgentConfig: AgentConfig{
LogLevel: "debug",
Port: "7002",
Datasets: agent.Datasets{agent.Dataset{Hash: dataHash, UserKey: pubPem.Bytes}},
Algorithm: agent.Algorithm{Hash: algoHash, UserKey: pubPem.Bytes},
ResultConsumers: []agent.ResultConsumer{{UserKey: pubPem.Bytes}},
AgentConfig: agent.AgentConfig{
LogLevel: "debug",
Port: "7002",
AttestedTls: attestedTLS,
},
}
fmt.Println(SendAgentConfig(3, ac))
@@ -98,7 +81,7 @@ func main() {
continue
}
conn.Close()
var mes manager.ClientStreamMessage
var mes pkgmanager.ClientStreamMessage
if err := proto.Unmarshal(b[:n], &mes); err != nil {
log.Println(err)
}
@@ -106,8 +89,8 @@ func main() {
}
}
func SendAgentConfig(cid uint32, ac Computation) error {
conn, err := vsock.Dial(cid, VsockConfigPort, nil)
func SendAgentConfig(cid uint32, ac agent.Computation) error {
conn, err := vsock.Dial(cid, qemu.VsockConfigPort, nil)
if err != nil {
return err
}
@@ -116,6 +99,11 @@ func SendAgentConfig(cid uint32, ac Computation) error {
if err != nil {
return err
}
var ac2 agent.Computation
if err := json.Unmarshal(payload, &ac2); err != nil {
return err
}
if _, err := conn.Write(payload); err != nil {
return err
}
+17
View File
@@ -0,0 +1,17 @@
# Algorithm
Agent accepts binaries programs. To use the python program you need to bundle or compile it.
In this example we'll use [pyinstaller](https://pypi.org/project/pyinstaller/)
```shell
pip install pandas scikit-learn
pip install -U pyinstaller
pyinstaller --onefile lin_reg.py
```
Make the binary static:
```shell
pip install staticx
staticx <dynamic_binary_file_path> <output_file_path>
```
+3 -3
View File
@@ -6,8 +6,8 @@ import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
dataset = sys.argv[1]
iris = pd.read_csv(io.StringIO(dataset))
csv_file_path = sys.argv[2]
iris = pd.read_csv(csv_file_path)
# Droping the Species since we only need the measurements
X = iris.drop(['Species'], axis=1)
@@ -30,7 +30,7 @@ joblib.dump(log_reg, model_buffer)
model_bytes = model_buffer.getvalue()
# Define the path for the Unix domain socket
socket_path = sys.argv[2]
socket_path = sys.argv[1]
# Create a Unix domain socket client
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

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