mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-08-07 23:31:55 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 93f2f2ab46 | |||
| dffefc45d8 | |||
| 9a5cc44b2a | |||
| 676c5f154c | |||
| 71104a210c | |||
| ec306c72b9 | |||
| 12a20c74f7 | |||
| d17eba35eb | |||
| cab2799741 | |||
| e217331e5e | |||
| 7e63921896 | |||
| ebe119686f | |||
| 3fe7507529 | |||
| 1b4a510eb2 | |||
| ff0fd71e7d | |||
| bfaa489bdb | |||
| c14f1d7b6c | |||
| ebc8f1bba4 | |||
| 293c65a3aa | |||
| 33744a12a8 | |||
| 4bb732ebf9 | |||
| 67f939fc66 | |||
| fa26573643 | |||
| 7d063aa48d | |||
| 12bd19b4c6 | |||
| 1210e238b4 | |||
| 93f9c4df1c | |||
| 913c6138cf | |||
| c6308f080b | |||
| cef9cbbecd | |||
| 7e17a00cb5 | |||
| 0e3c0c04af | |||
| bb0ad293e6 | |||
| 3e99214d2a |
@@ -16,7 +16,3 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
groups:
|
||||
go-dependency:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Set up protoc
|
||||
run: |
|
||||
PROTOC_VERSION=29.0
|
||||
PROTOC_GEN_VERSION=v1.36.0
|
||||
PROTOC_GEN_VERSION=v1.36.5
|
||||
PROTOC_GRPC_VERSION=v1.5.1
|
||||
|
||||
# Download and install protoc
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Set up Cocos-AI
|
||||
run: |
|
||||
# Rename .pb.go files to .pb.go.tmp to prevent conflicts
|
||||
for p in $(ls pkg/manager/*.pb.go); do
|
||||
for p in $(ls manager/*.pb.go); do
|
||||
mv $p $p.tmp
|
||||
done
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
make protoc
|
||||
|
||||
# Compare generated Go files with the original ones
|
||||
for p in $(ls pkg/manager/*.pb.go); do
|
||||
for p in $(ls manager/*.pb.go); do
|
||||
if ! cmp -s $p $p.tmp; then
|
||||
echo "Proto file and generated Go file $p are out of sync!"
|
||||
exit 1
|
||||
|
||||
@@ -22,10 +22,11 @@ jobs:
|
||||
go-version: 1.23.x
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v7
|
||||
with:
|
||||
version: v1.60
|
||||
version: v2.0.2
|
||||
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
make
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
mkdir coverage
|
||||
|
||||
- name: Run Agent tests
|
||||
run: go test -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
|
||||
run: go test --tags embed -v --race -covermode=atomic -coverprofile coverage/agent.out ./agent/...
|
||||
|
||||
- name: Run cli tests
|
||||
run: go test -v --race -covermode=atomic -coverprofile coverage/cli.out ./cli/...
|
||||
|
||||
+83
-63
@@ -1,80 +1,100 @@
|
||||
run:
|
||||
timeout: 3m
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 10
|
||||
max-same-issues: 10
|
||||
|
||||
linters-settings:
|
||||
importas:
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
alias:
|
||||
- pkg: github.com/absmach/magistrala/logger
|
||||
alias: mglog
|
||||
|
||||
gocritic:
|
||||
enabled-checks:
|
||||
- dupImport
|
||||
- importShadow
|
||||
- httpNoBody
|
||||
- paramTypeCombine
|
||||
- emptyStringTest
|
||||
- builtinShadow
|
||||
- exposedSyncMutex
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
disabled-tags:
|
||||
- performance
|
||||
- style
|
||||
- experimental
|
||||
- opinionated
|
||||
stylecheck:
|
||||
checks: ["-ST1000", "-ST1003", "-ST1020", "-ST1021", "-ST1022"]
|
||||
goheader:
|
||||
template: |-
|
||||
Copyright (c) Ultraviolet
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- importas
|
||||
- gocritic
|
||||
- gosimple
|
||||
- errcheck
|
||||
- govet
|
||||
- unused
|
||||
- goconst
|
||||
- godot
|
||||
- godox
|
||||
- ineffassign
|
||||
- misspell
|
||||
- stylecheck
|
||||
- whitespace
|
||||
- gci
|
||||
- gofmt
|
||||
- goimports
|
||||
- loggercheck
|
||||
- goheader
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- contextcheck
|
||||
- copyloopvar
|
||||
- decorder
|
||||
- dogsled
|
||||
- dupword
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- execinquery
|
||||
- copyloopvar
|
||||
- ginkgolinter
|
||||
- gocheckcompilerdirectives
|
||||
- gofumpt
|
||||
- goconst
|
||||
- gocritic
|
||||
- godot
|
||||
- godox
|
||||
- goheader
|
||||
- goprintffuncname
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- loggercheck
|
||||
- makezero
|
||||
- mirror
|
||||
- misspell
|
||||
- nakedret
|
||||
- dupword
|
||||
- staticcheck
|
||||
- unused
|
||||
- whitespace
|
||||
settings:
|
||||
gocritic:
|
||||
enabled-checks:
|
||||
- dupImport
|
||||
- importShadow
|
||||
- httpNoBody
|
||||
- paramTypeCombine
|
||||
- emptyStringTest
|
||||
- builtinShadow
|
||||
- exposedSyncMutex
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
disabled-tags:
|
||||
- performance
|
||||
- style
|
||||
- experimental
|
||||
- opinionated
|
||||
goheader:
|
||||
template: |-
|
||||
Copyright (c) Ultraviolet
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
importas:
|
||||
alias:
|
||||
- pkg: github.com/absmach/magistrala/logger
|
||||
alias: mglog
|
||||
no-unaliased: true
|
||||
no-extra-aliases: false
|
||||
staticcheck:
|
||||
checks:
|
||||
- -ST1000
|
||||
- -ST1003
|
||||
- -ST1020
|
||||
- -ST1021
|
||||
- -ST1022
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- makezero
|
||||
text: with non-zero initialized length
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
max-issues-per-linter: 10
|
||||
max-same-issues: 10
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
@@ -12,6 +12,7 @@ CONFIG_DIR ?= /etc/cocos
|
||||
SERVICE_NAME ?= cocos-manager
|
||||
SERVICE_DIR ?= /etc/systemd/system
|
||||
SERVICE_FILE = init/systemd/$(SERVICE_NAME).service
|
||||
IGVM_BUILD_SCRIPT := ./scripts/igvmmeasure/igvm.sh
|
||||
|
||||
define compile_service
|
||||
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
|
||||
@@ -27,8 +28,9 @@ endef
|
||||
|
||||
all: $(SERVICES)
|
||||
|
||||
$(SERVICES):
|
||||
$(SERVICES):
|
||||
$(call compile_service,$@)
|
||||
@if [ "$@" = "cli" ] || [ "$@" = "manager" ]; then $(MAKE) build-igvm; fi
|
||||
|
||||
$(ATTESTATION_POLICY):
|
||||
$(MAKE) -C ./scripts/attestation_policy
|
||||
@@ -61,3 +63,7 @@ stop:
|
||||
install_service:
|
||||
sudo install -m 644 $(SERVICE_FILE) $(SERVICE_DIR)/$(SERVICE_NAME).service
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
build-igvm:
|
||||
@echo "Running build script for igvmmeasure..."
|
||||
@$(IGVM_BUILD_SCRIPT)
|
||||
|
||||
@@ -76,3 +76,5 @@ Cocos AI is published under the permissive open-source [Apache-2.0](LICENSE) lic
|
||||
- [Official Releases](https://github.com/ultravioletrs/cocos/releases)
|
||||
- [Confidential Computing Overview](https://confidentialcomputing.io/white-papers-reports/)
|
||||
- [Trusted Execution Environments (TEEs)](https://en.wikipedia.org/wiki/Trusted_execution_environment)
|
||||
|
||||
>This work has been partially supported by the ELASTIC project, which received funding from the Smart Networks and Services Joint Undertaking (SNS JU) under the European Union’s Horizon Europe research and innovation programme under Grant Agreement No 101139067. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union. Neither the European Union nor the granting authority can be held responsible for them.
|
||||
|
||||
+10
-10
@@ -6,16 +6,16 @@ Agent service provides a barebones HTTP and gRPC API and Service interface imple
|
||||
|
||||
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ----------------------------- | ------------------------------------------------------ | ------------------------------ |
|
||||
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | info |
|
||||
| AGENT_GRPC_HOST | Agent service gRPC host | "" |
|
||||
| AGENT_GRPC_PORT | Agent service gRPC port | 7002 |
|
||||
| AGENT_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
|
||||
| AGENT_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
|
||||
| AGENT_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | "" |
|
||||
| AGENT_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | "" |
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| AGENT_LOG_LEVEL | Log level for agent service (debug, info, warn, error) | debug |
|
||||
| AGENT_CVM_GRPC_HOST | Agent service gRPC host | "" |
|
||||
| AGENT_CVM_GRPC_PORT | Agent service gRPC port | 7001 |
|
||||
| AGENT_CVM_GRPC_SERVER_CERT | Path to gRPC server certificate in pem format | "" |
|
||||
| AGENT_CVM_GRPC_SERVER_KEY | Path to gRPC server key in pem format | "" |
|
||||
| AGENT_CVM_GRPC_SERVER_CA_CERTS | Path to gRPC server CA certificate | "" |
|
||||
| AGENT_CVM_GRPC_CLIENT_CA_CERTS | Path to gRPC client CA certificate | "" |
|
||||
| AGENT_CVM_CA_URL | URL for CA service, if provided it will be used for certificate generation, used only with aTLS at the moment | "" |
|
||||
|
||||
## Deployment
|
||||
|
||||
|
||||
+53
-34
@@ -3,7 +3,7 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.0
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc v5.29.0
|
||||
// source: agent/agent.proto
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -281,7 +282,9 @@ func (x *ResultResponse) GetFile() []byte {
|
||||
|
||||
type AttestationRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ReportData []byte `protobuf:"bytes,1,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"` // Should be of length 64.
|
||||
TeeNonce []byte `protobuf:"bytes,1,opt,name=teeNonce,proto3" json:"teeNonce,omitempty"` // Should be less or equal 64 bytes.
|
||||
VtpmNonce []byte `protobuf:"bytes,2,opt,name=vtpmNonce,proto3" json:"vtpmNonce,omitempty"` // Should be less or equal 32 bytes.
|
||||
Type int32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -316,13 +319,27 @@ func (*AttestationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_agent_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *AttestationRequest) GetReportData() []byte {
|
||||
func (x *AttestationRequest) GetTeeNonce() []byte {
|
||||
if x != nil {
|
||||
return x.ReportData
|
||||
return x.TeeNonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AttestationRequest) GetVtpmNonce() []byte {
|
||||
if x != nil {
|
||||
return x.VtpmNonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AttestationRequest) GetType() int32 {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AttestationResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
File []byte `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
|
||||
@@ -369,7 +386,7 @@ func (x *AttestationResponse) GetFile() []byte {
|
||||
|
||||
var File_agent_agent_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_agent_agent_proto_rawDesc = []byte{
|
||||
var file_agent_agent_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x11, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x22, 0x4f, 0x0a, 0x0b, 0x41, 0x6c,
|
||||
0x67, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x6c, 0x67,
|
||||
@@ -386,40 +403,43 @@ var file_agent_agent_proto_rawDesc = []byte{
|
||||
0x22, 0x0f, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x22, 0x24, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x35, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x29,
|
||||
0x0a, 0x13, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c,
|
||||
0x67, 0x6f, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
|
||||
0x6c, 0x67, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12,
|
||||
0x33, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x28, 0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73,
|
||||
0x75, 0x6c, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12,
|
||||
0x48, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
0x0c, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x62, 0x0a, 0x12, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x08, 0x74, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x76, 0x74, 0x70,
|
||||
0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x76, 0x74,
|
||||
0x70, 0x6d, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x29, 0x0a, 0x13, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x32, 0xfd, 0x01, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x41, 0x6c, 0x67, 0x6f, 0x12,
|
||||
0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x6c, 0x67, 0x6f,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x33, 0x0a, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74,
|
||||
0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28,
|
||||
0x01, 0x12, 0x39, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x15, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0b,
|
||||
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
file_agent_agent_proto_rawDescOnce sync.Once
|
||||
file_agent_agent_proto_rawDescData = file_agent_agent_proto_rawDesc
|
||||
file_agent_agent_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_agent_agent_proto_rawDescGZIP() []byte {
|
||||
file_agent_agent_proto_rawDescOnce.Do(func() {
|
||||
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_agent_proto_rawDescData)
|
||||
file_agent_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)))
|
||||
})
|
||||
return file_agent_agent_proto_rawDescData
|
||||
}
|
||||
@@ -460,7 +480,7 @@ func file_agent_agent_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_agent_agent_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
@@ -471,7 +491,6 @@ func file_agent_agent_proto_init() {
|
||||
MessageInfos: file_agent_agent_proto_msgTypes,
|
||||
}.Build()
|
||||
File_agent_agent_proto = out.File
|
||||
file_agent_agent_proto_rawDesc = nil
|
||||
file_agent_agent_proto_goTypes = nil
|
||||
file_agent_agent_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
+3
-1
@@ -36,7 +36,9 @@ message ResultResponse {
|
||||
}
|
||||
|
||||
message AttestationRequest {
|
||||
bytes report_data = 1; // Should be of length 64.
|
||||
bytes teeNonce = 1; // Should be less or equal 64 bytes.
|
||||
bytes vtpmNonce = 2; // Should be less or equal 32 bytes.
|
||||
int32 type = 3;
|
||||
}
|
||||
|
||||
message AttestationResponse {
|
||||
|
||||
@@ -62,7 +62,7 @@ func (d *docker) Run() error {
|
||||
|
||||
ctx := context.Background()
|
||||
// Load the Docker image from the tar file.
|
||||
resp, err := cli.ImageLoad(ctx, imageFile, true)
|
||||
resp, err := cli.ImageLoad(ctx, imageFile, client.ImageLoadWithQuiet(true))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load Docker image from file: %v", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func algoEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
@@ -70,7 +71,7 @@ func attestationEndpoint(svc agent.Service) endpoint.Endpoint {
|
||||
if err := req.validate(); err != nil {
|
||||
return attestationRes{}, err
|
||||
}
|
||||
file, err := svc.Attestation(ctx, req.ReportData)
|
||||
file, err := svc.Attestation(ctx, req.TeeNonce, req.VtpmNonce, config.AttestationType(req.AttType))
|
||||
if err != nil {
|
||||
return attestationRes{}, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -141,11 +142,11 @@ func TestAttestationEndpoint(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
|
||||
},
|
||||
{
|
||||
name: "Service Error",
|
||||
req: attestationReq{ReportData: sha3.Sum512([]byte("report data"))},
|
||||
req: attestationReq{TeeNonce: sha3.Sum512([]byte("report data")), VtpmNonce: sha3.Sum256([]byte("vtpm nonce")), AttType: config.SNP},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
@@ -153,9 +154,9 @@ func TestAttestationEndpoint(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.name == svcErr {
|
||||
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, errors.New("")).Once()
|
||||
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, errors.New("")).Once()
|
||||
} else {
|
||||
svc.On("Attestation", context.Background(), tt.req.ReportData).Return([]byte{}, nil).Once()
|
||||
svc.On("Attestation", context.Background(), tt.req.TeeNonce, tt.req.VtpmNonce, tt.req.AttType).Return([]byte{}, nil).Once()
|
||||
}
|
||||
endpoint := attestationEndpoint(svc)
|
||||
res, err := endpoint(context.Background(), tt.req)
|
||||
|
||||
@@ -4,6 +4,10 @@ package grpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
type algoReq struct {
|
||||
@@ -38,9 +42,16 @@ func (req resultReq) validate() error {
|
||||
}
|
||||
|
||||
type attestationReq struct {
|
||||
ReportData [64]byte
|
||||
TeeNonce [quoteprovider.Nonce]byte
|
||||
VtpmNonce [vtpm.Nonce]byte
|
||||
AttType config.AttestationType
|
||||
}
|
||||
|
||||
func (req attestationReq) validate() error {
|
||||
return nil
|
||||
switch req.AttType {
|
||||
case config.SNP, config.VTPM, config.SNPvTPM:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid attestation type in attestation request")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/transport/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
@@ -21,6 +24,11 @@ const (
|
||||
FileSizeKey = "file-size"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTEENonceLength = errors.New("malformed report data, expect less or equal to 64 bytes")
|
||||
ErrVTpmNonceLength = errors.New("malformed vTPM nonce, expect less or equal to 32 bytes")
|
||||
)
|
||||
|
||||
var _ agent.AgentServiceServer = (*grpcServer)(nil)
|
||||
|
||||
type grpcServer struct {
|
||||
@@ -96,10 +104,20 @@ func encodeResultResponse(_ context.Context, response interface{}) (interface{},
|
||||
|
||||
func decodeAttestationRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
|
||||
req := grpcReq.(*agent.AttestationRequest)
|
||||
if len(req.ReportData) != agent.ReportDataSize {
|
||||
return nil, errors.New("malformed report data, expect 64 bytes")
|
||||
var reportData [quoteprovider.Nonce]byte
|
||||
var nonce [vtpm.Nonce]byte
|
||||
|
||||
if len(req.TeeNonce) > quoteprovider.Nonce {
|
||||
return nil, ErrTEENonceLength
|
||||
}
|
||||
return attestationReq{ReportData: [agent.ReportDataSize]byte(req.ReportData)}, nil
|
||||
|
||||
if len(req.VtpmNonce) > vtpm.Nonce {
|
||||
return nil, ErrVTpmNonceLength
|
||||
}
|
||||
|
||||
copy(reportData[:], req.TeeNonce)
|
||||
copy(nonce[:], req.VtpmNonce)
|
||||
return attestationReq{TeeNonce: reportData, VtpmNonce: nonce, AttType: config.AttestationType(req.Type)}, nil
|
||||
}
|
||||
|
||||
func encodeAttestationResponse(_ context.Context, response interface{}) (interface{}, error) {
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
@@ -151,10 +154,12 @@ func TestAttestation(t *testing.T) {
|
||||
mockStream := &MockAgentService_AttestationServer{ctx: context.Background()}
|
||||
mockStream.On("Send", mock.AnythingOfType("*agent.AttestationResponse")).Return(nil)
|
||||
|
||||
reportData := [agent.ReportDataSize]byte{}
|
||||
mockService.On("Attestation", mock.Anything, reportData).Return([]byte("attestation data"), nil)
|
||||
reportData := [quoteprovider.Nonce]byte{}
|
||||
vtpmNonce := [vtpm.Nonce]byte{}
|
||||
attestationType := config.SNP
|
||||
mockService.On("Attestation", mock.Anything, reportData, vtpmNonce, attestationType).Return([]byte("attestation data"), nil)
|
||||
|
||||
err := server.Attestation(&agent.AttestationRequest{ReportData: reportData[:]}, mockStream)
|
||||
err := server.Attestation(&agent.AttestationRequest{TeeNonce: reportData[:]}, mockStream)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockService.AssertExpectations(t)
|
||||
@@ -199,11 +204,11 @@ func TestEncodeResultResponse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeAttestationRequest(t *testing.T) {
|
||||
reportData := [agent.ReportDataSize]byte{}
|
||||
req := &agent.AttestationRequest{ReportData: reportData[:]}
|
||||
nonce := [quoteprovider.Nonce]byte{}
|
||||
req := &agent.AttestationRequest{TeeNonce: nonce[:]}
|
||||
decoded, err := decodeAttestationRequest(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, attestationReq{ReportData: reportData}, decoded)
|
||||
assert.Equal(t, attestationReq{TeeNonce: nonce}, decoded)
|
||||
}
|
||||
|
||||
func TestEncodeAttestationResponse(t *testing.T) {
|
||||
|
||||
+14
-2
@@ -13,6 +13,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
var _ agent.Service = (*loggingMiddleware)(nil)
|
||||
@@ -27,6 +30,15 @@ func LoggingMiddleware(svc agent.Service, logger *slog.Logger) agent.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
// State implements agent.Service.
|
||||
func (lm *loggingMiddleware) State() (state string) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method State took %s to complete with state %s", time.Since(begin), state)
|
||||
lm.logger.Debug(message)
|
||||
}(time.Now())
|
||||
return lm.svc.State()
|
||||
}
|
||||
|
||||
// InitComputation implements agent.Service.
|
||||
func (lm *loggingMiddleware) InitComputation(ctx context.Context, cmp agent.Computation) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
@@ -94,7 +106,7 @@ func (lm *loggingMiddleware) Result(ctx context.Context) (response []byte, err e
|
||||
return lm.svc.Result(ctx)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) (response []byte, err error) {
|
||||
func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) (response []byte, err error) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method Attestation took %s to complete", time.Since(begin))
|
||||
if err != nil {
|
||||
@@ -104,5 +116,5 @@ func (lm *loggingMiddleware) Attestation(ctx context.Context, reportData [agent.
|
||||
lm.logger.Info(fmt.Sprintf("%s without errors", message))
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.Attestation(ctx, reportData)
|
||||
return lm.svc.Attestation(ctx, reportData, nonce, attType)
|
||||
}
|
||||
|
||||
+15
-2
@@ -12,6 +12,9 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
var _ agent.Service = (*metricsMiddleware)(nil)
|
||||
@@ -32,6 +35,16 @@ func MetricsMiddleware(svc agent.Service, counter metrics.Counter, latency metri
|
||||
}
|
||||
}
|
||||
|
||||
// State implements agent.Service.
|
||||
func (ms *metricsMiddleware) State() string {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "state").Add(1)
|
||||
ms.latency.With("method", "state").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.State()
|
||||
}
|
||||
|
||||
// InitComputation implements agent.Service.
|
||||
func (ms *metricsMiddleware) InitComputation(ctx context.Context, cmp agent.Computation) error {
|
||||
defer func(begin time.Time) {
|
||||
@@ -79,11 +92,11 @@ func (ms *metricsMiddleware) Result(ctx context.Context) ([]byte, error) {
|
||||
return ms.svc.Result(ctx)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [agent.ReportDataSize]byte) ([]byte, error) {
|
||||
func (ms *metricsMiddleware) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "attestation").Add(1)
|
||||
ms.latency.With("method", "attestation").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.Attestation(ctx, reportData)
|
||||
return ms.svc.Attestation(ctx, reportData, nonce, attType)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
var _ fmt.Stringer = (*Datasets)(nil)
|
||||
|
||||
type AgentConfig struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
CertFile string `json:"cert_file,omitempty"`
|
||||
KeyFile string `json:"server_key,omitempty"`
|
||||
|
||||
@@ -106,7 +106,6 @@ func TestDecompressToContext(t *testing.T) {
|
||||
|
||||
func TestAgentConfigJSON(t *testing.T) {
|
||||
config := AgentConfig{
|
||||
Host: "localhost",
|
||||
Port: "8080",
|
||||
CertFile: "cert.pem",
|
||||
KeyFile: "key.pem",
|
||||
|
||||
+122
-23
@@ -11,17 +11,29 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms/server"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
reconnectInterval = 5 * time.Second
|
||||
sendTimeout = 5 * time.Second
|
||||
pendingMsgFile = "pending_messages.json"
|
||||
)
|
||||
|
||||
var (
|
||||
errCorruptedManifest = errors.New("received manifest may be corrupted")
|
||||
errUnknonwMessageType = errors.New("unknown message type")
|
||||
sendTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type PendingMessage struct {
|
||||
Message *cvms.ClientStreamMessage
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type CVMSClient struct {
|
||||
mu sync.Mutex
|
||||
stream cvms.Service_ProcessClient
|
||||
@@ -30,21 +42,55 @@ type CVMSClient struct {
|
||||
logger *slog.Logger
|
||||
runReqManager *runRequestManager
|
||||
sp server.AgentServer
|
||||
storage storage.Storage
|
||||
reconnectFn func(context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error)
|
||||
grpcClient pkggrpc.Client
|
||||
}
|
||||
|
||||
// NewClient returns new gRPC client instance.
|
||||
func NewClient(stream cvms.Service_ProcessClient, svc agent.Service, messageQueue chan *cvms.ClientStreamMessage, logger *slog.Logger, sp server.AgentServer) CVMSClient {
|
||||
return CVMSClient{
|
||||
func NewClient(stream cvms.Service_ProcessClient, svc agent.Service, messageQueue chan *cvms.ClientStreamMessage, logger *slog.Logger, sp server.AgentServer, storageDir string, reconnectFn func(context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error), grpcClient pkggrpc.Client) (*CVMSClient, error) {
|
||||
store, err := storage.NewFileStorage(storageDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CVMSClient{
|
||||
stream: stream,
|
||||
svc: svc,
|
||||
messageQueue: messageQueue,
|
||||
logger: logger,
|
||||
runReqManager: newRunRequestManager(),
|
||||
sp: sp,
|
||||
}
|
||||
storage: store,
|
||||
reconnectFn: reconnectFn,
|
||||
grpcClient: grpcClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (client *CVMSClient) Process(ctx context.Context, cancel context.CancelFunc) error {
|
||||
for {
|
||||
err := client.processWithRetry(ctx)
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
client.logger.Info("Connection lost, attempting to reconnect...", "error", err)
|
||||
time.Sleep(reconnectInterval)
|
||||
|
||||
grpcClient, stream, err := client.reconnectFn(ctx)
|
||||
if err != nil {
|
||||
client.logger.Error("Failed to reconnect", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
client.mu.Lock()
|
||||
client.stream = stream
|
||||
client.grpcClient = grpcClient
|
||||
client.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (client *CVMSClient) processWithRetry(ctx context.Context) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
eg.Go(func() error {
|
||||
@@ -75,18 +121,87 @@ func (client *CVMSClient) handleIncomingMessages(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (client *CVMSClient) handleOutgoingMessages(ctx context.Context) error {
|
||||
pendingMsgs, err := client.storage.Load()
|
||||
if err != nil {
|
||||
client.logger.Error("Failed to load pending messages", "error", err)
|
||||
} else {
|
||||
client.sendPendingMessages(pendingMsgs)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case msg := <-client.messageQueue:
|
||||
if err := client.sendStreamMessage(msg); err != nil {
|
||||
if err := client.storage.Add(msg); err != nil {
|
||||
client.logger.Error("Failed to store pending message", "error", err)
|
||||
}
|
||||
client.logger.Error("Failed to send message, stored for retry", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *CVMSClient) sendStreamMessage(msg *cvms.ClientStreamMessage) error {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
|
||||
return client.stream.Send(msg)
|
||||
}
|
||||
|
||||
func (client *CVMSClient) sendPendingMessages(pending []storage.Message) {
|
||||
for _, pm := range pending {
|
||||
if err := client.sendStreamMessage(pm.Message); err != nil {
|
||||
if err := client.storage.Add(pm.Message); err != nil {
|
||||
client.logger.Error("Failed to store pending message", "error", err)
|
||||
}
|
||||
client.logger.Error("Failed to resend pending message", "error", err)
|
||||
} else {
|
||||
client.logger.Info("Successfully resent pending message")
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.storage.Clear(); err != nil {
|
||||
client.logger.Error("Failed to clear pending messages", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (client *CVMSClient) processIncomingMessage(ctx context.Context, req *cvms.ServerStreamMessage) error {
|
||||
switch mes := req.Message.(type) {
|
||||
case *cvms.ServerStreamMessage_RunReqChunks:
|
||||
return client.handleRunReqChunks(ctx, mes)
|
||||
case *cvms.ServerStreamMessage_StopComputation:
|
||||
go client.handleStopComputation(ctx, mes)
|
||||
case *cvms.ServerStreamMessage_AgentStateReq:
|
||||
client.handleAgentStateReq(mes)
|
||||
case *cvms.ServerStreamMessage_DisconnectReq:
|
||||
client.logger.Info("Received disconnect request")
|
||||
client.mu.Lock()
|
||||
if err := client.grpcClient.Close(); err != nil {
|
||||
client.logger.Error("Failed to close gRPC client", "error", err)
|
||||
}
|
||||
client.mu.Unlock()
|
||||
default:
|
||||
return errUnknonwMessageType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *CVMSClient) handleAgentStateReq(mes *cvms.ServerStreamMessage_AgentStateReq) {
|
||||
state := client.svc.State()
|
||||
|
||||
msg := &cvms.ClientStreamMessage_AgentStateRes{
|
||||
AgentStateRes: &cvms.AgentStateRes{
|
||||
State: state,
|
||||
Id: mes.AgentStateReq.Id,
|
||||
},
|
||||
}
|
||||
|
||||
client.sendMessage(&cvms.ClientStreamMessage{Message: msg})
|
||||
}
|
||||
|
||||
func (client *CVMSClient) handleRunReqChunks(ctx context.Context, msg *cvms.ServerStreamMessage_RunReqChunks) error {
|
||||
buffer, complete := client.runReqManager.addChunk(msg.RunReqChunks.Id, msg.RunReqChunks.Data, msg.RunReqChunks.IsLast)
|
||||
|
||||
@@ -147,16 +262,14 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
|
||||
},
|
||||
}
|
||||
|
||||
err := client.sp.Start(ctx, agent.AgentConfig{
|
||||
if err := client.sp.Start(agent.AgentConfig{
|
||||
Port: runReq.AgentConfig.Port,
|
||||
Host: runReq.AgentConfig.Host,
|
||||
CertFile: runReq.AgentConfig.CertFile,
|
||||
KeyFile: runReq.AgentConfig.KeyFile,
|
||||
ServerCAFile: runReq.AgentConfig.ServerCaFile,
|
||||
ClientCAFile: runReq.AgentConfig.ClientCaFile,
|
||||
AttestedTls: runReq.AgentConfig.AttestedTls,
|
||||
}, ac)
|
||||
if err != nil {
|
||||
}, ac); err != nil {
|
||||
client.logger.Warn(err.Error())
|
||||
runRes.RunRes.Error = err.Error()
|
||||
}
|
||||
@@ -175,28 +288,14 @@ func (client *CVMSClient) handleStopComputation(ctx context.Context, mes *cvms.S
|
||||
}
|
||||
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
|
||||
if err := client.sp.Stop(); err != nil {
|
||||
msg.StopComputationRes.Message = err.Error()
|
||||
}
|
||||
client.mu.Unlock()
|
||||
|
||||
client.sendMessage(&cvms.ClientStreamMessage{Message: msg})
|
||||
}
|
||||
|
||||
func (client *CVMSClient) handleOutgoingMessages(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case mes := <-client.messageQueue:
|
||||
if err := client.stream.Send(mes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *CVMSClient) sendMessage(mes *cvms.ClientStreamMessage) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), sendTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
servermocks "github.com/ultravioletrs/cocos/agent/cvms/server/mocks"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
clientmocks "github.com/ultravioletrs/cocos/pkg/clients/grpc/mocks"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -32,16 +34,16 @@ func (m *mockStream) Send(msg *cvms.ClientStreamMessage) error {
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestManagerClient_Process1(t *testing.T) {
|
||||
func TestManagerClient_Process(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupMocks func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider)
|
||||
setupMocks func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer)
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "Stop computation",
|
||||
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider) {
|
||||
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
|
||||
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
|
||||
Message: &cvms.ServerStreamMessage_StopComputation{
|
||||
StopComputation: &cvms.StopComputation{},
|
||||
@@ -56,7 +58,7 @@ func TestManagerClient_Process1(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Run request chunks",
|
||||
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider) {
|
||||
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
|
||||
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{
|
||||
Message: &cvms.ServerStreamMessage_RunReqChunks{
|
||||
RunReqChunks: &cvms.RunReqChunks{},
|
||||
@@ -69,7 +71,7 @@ func TestManagerClient_Process1(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Receive error",
|
||||
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServerProvider) {
|
||||
setupMocks: func(mockStream *mockStream, mockSvc *mocks.Service, mockServerSvc *servermocks.AgentServer) {
|
||||
mockStream.On("Recv").Return(&cvms.ServerStreamMessage{}, assert.AnError)
|
||||
},
|
||||
expectError: true,
|
||||
@@ -80,18 +82,25 @@ func TestManagerClient_Process1(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockStream := new(mockStream)
|
||||
mockSvc := new(mocks.Service)
|
||||
mockServerSvc := new(servermocks.AgentServerProvider)
|
||||
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
|
||||
mockServerSvc := new(servermocks.AgentServer)
|
||||
messageQueue := make(chan *cvms.ClientStreamMessage)
|
||||
logger := mglog.NewMock()
|
||||
|
||||
client := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc)
|
||||
go func() {
|
||||
<-messageQueue
|
||||
}()
|
||||
|
||||
grpcClient := new(clientmocks.Client)
|
||||
|
||||
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
tc.setupMocks(mockStream, mockSvc, mockServerSvc)
|
||||
|
||||
err := client.Process(ctx, cancel)
|
||||
err = client.Process(ctx, cancel)
|
||||
|
||||
if tc.expectError {
|
||||
assert.Error(t, err)
|
||||
@@ -108,11 +117,13 @@ func TestManagerClient_Process1(t *testing.T) {
|
||||
func TestManagerClient_handleRunReqChunks(t *testing.T) {
|
||||
mockStream := new(mockStream)
|
||||
mockSvc := new(mocks.Service)
|
||||
mockServerSvc := new(servermocks.AgentServerProvider)
|
||||
mockServerSvc := new(servermocks.AgentServer)
|
||||
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
|
||||
logger := mglog.NewMock()
|
||||
grpcClient := new(clientmocks.Client)
|
||||
|
||||
client := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc)
|
||||
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
runReq := &cvms.ComputationRunReq{
|
||||
Id: "test-id",
|
||||
@@ -137,7 +148,7 @@ func TestManagerClient_handleRunReqChunks(t *testing.T) {
|
||||
mockSvc.On("InitComputation", mock.Anything, mock.Anything).Return(nil)
|
||||
mockServerSvc.On("Start", mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
|
||||
err := client.handleRunReqChunks(context.Background(), chunk1)
|
||||
err = client.handleRunReqChunks(context.Background(), chunk1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = client.handleRunReqChunks(context.Background(), chunk2)
|
||||
@@ -158,11 +169,13 @@ func TestManagerClient_handleRunReqChunks(t *testing.T) {
|
||||
func TestManagerClient_handleStopComputation(t *testing.T) {
|
||||
mockStream := new(mockStream)
|
||||
mockSvc := new(mocks.Service)
|
||||
mockServerSvc := new(servermocks.AgentServerProvider)
|
||||
mockServerSvc := new(servermocks.AgentServer)
|
||||
messageQueue := make(chan *cvms.ClientStreamMessage, 10)
|
||||
logger := mglog.NewMock()
|
||||
grpcClient := new(clientmocks.Client)
|
||||
|
||||
client := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc)
|
||||
client, err := NewClient(mockStream, mockSvc, messageQueue, logger, mockServerSvc, t.TempDir(), func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) { return nil, nil, nil }, grpcClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
stopReq := &cvms.ServerStreamMessage_StopComputation{
|
||||
StopComputation: &cvms.StopComputation{
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
cvms "github.com/ultravioletrs/cocos/agent/cvms"
|
||||
|
||||
storage "github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
|
||||
)
|
||||
|
||||
// Storage is an autogenerated mock type for the Storage type
|
||||
type Storage struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Storage_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Storage) EXPECT() *Storage_Expecter {
|
||||
return &Storage_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Add provides a mock function with given fields: msg
|
||||
func (_m *Storage) Add(msg *cvms.ClientStreamMessage) error {
|
||||
ret := _m.Called(msg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Add")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*cvms.ClientStreamMessage) error); ok {
|
||||
r0 = rf(msg)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Storage_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add'
|
||||
type Storage_Add_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Add is a helper method to define mock.On call
|
||||
// - msg *cvms.ClientStreamMessage
|
||||
func (_e *Storage_Expecter) Add(msg interface{}) *Storage_Add_Call {
|
||||
return &Storage_Add_Call{Call: _e.mock.On("Add", msg)}
|
||||
}
|
||||
|
||||
func (_c *Storage_Add_Call) Run(run func(msg *cvms.ClientStreamMessage)) *Storage_Add_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*cvms.ClientStreamMessage))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Add_Call) Return(_a0 error) *Storage_Add_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Add_Call) RunAndReturn(run func(*cvms.ClientStreamMessage) error) *Storage_Add_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Clear provides a mock function with no fields
|
||||
func (_m *Storage) Clear() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Clear")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Storage_Clear_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Clear'
|
||||
type Storage_Clear_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Clear is a helper method to define mock.On call
|
||||
func (_e *Storage_Expecter) Clear() *Storage_Clear_Call {
|
||||
return &Storage_Clear_Call{Call: _e.mock.On("Clear")}
|
||||
}
|
||||
|
||||
func (_c *Storage_Clear_Call) Run(run func()) *Storage_Clear_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Clear_Call) Return(_a0 error) *Storage_Clear_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Clear_Call) RunAndReturn(run func() error) *Storage_Clear_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Load provides a mock function with no fields
|
||||
func (_m *Storage) Load() ([]storage.Message, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Load")
|
||||
}
|
||||
|
||||
var r0 []storage.Message
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]storage.Message, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []storage.Message); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]storage.Message)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Storage_Load_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Load'
|
||||
type Storage_Load_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Load is a helper method to define mock.On call
|
||||
func (_e *Storage_Expecter) Load() *Storage_Load_Call {
|
||||
return &Storage_Load_Call{Call: _e.mock.On("Load")}
|
||||
}
|
||||
|
||||
func (_c *Storage_Load_Call) Run(run func()) *Storage_Load_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Load_Call) Return(_a0 []storage.Message, _a1 error) *Storage_Load_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Load_Call) RunAndReturn(run func() ([]storage.Message, error)) *Storage_Load_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Save provides a mock function with given fields: messages
|
||||
func (_m *Storage) Save(messages []storage.Message) error {
|
||||
ret := _m.Called(messages)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Save")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func([]storage.Message) error); ok {
|
||||
r0 = rf(messages)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Storage_Save_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Save'
|
||||
type Storage_Save_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Save is a helper method to define mock.On call
|
||||
// - messages []storage.Message
|
||||
func (_e *Storage_Expecter) Save(messages interface{}) *Storage_Save_Call {
|
||||
return &Storage_Save_Call{Call: _e.mock.On("Save", messages)}
|
||||
}
|
||||
|
||||
func (_c *Storage_Save_Call) Run(run func(messages []storage.Message)) *Storage_Save_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]storage.Message))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Save_Call) Return(_a0 error) *Storage_Save_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Storage_Save_Call) RunAndReturn(run func([]storage.Message) error) *Storage_Save_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewStorage(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Storage {
|
||||
mock := &Storage{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
)
|
||||
|
||||
// Message represents a pending message with its timestamp.
|
||||
type Message struct {
|
||||
Message *cvms.ClientStreamMessage
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// Storage defines the interface for message persistence operations.
|
||||
type Storage interface {
|
||||
// Load retrieves all pending messages from storage.
|
||||
Load() ([]Message, error)
|
||||
|
||||
// Save persists the given messages to storage.
|
||||
Save(messages []Message) error
|
||||
|
||||
// Add appends a new message to storage.
|
||||
Add(msg *cvms.ClientStreamMessage) error
|
||||
|
||||
// Clear removes all messages from storage.
|
||||
Clear() error
|
||||
}
|
||||
|
||||
// FileStorage implements Storage interface using file-based persistence.
|
||||
type FileStorage struct {
|
||||
mu sync.Mutex
|
||||
path string
|
||||
msgs []Message
|
||||
}
|
||||
|
||||
// NewFileStorage creates a new file-based storage instance.
|
||||
func NewFileStorage(storageDir string) (*FileStorage, error) {
|
||||
if err := os.MkdirAll(storageDir, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileStorage{
|
||||
path: filepath.Join(storageDir, "pending_messages.json"),
|
||||
msgs: make([]Message, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *FileStorage) Load() ([]Message, error) {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(fs.path)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &fs.msgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fs.msgs, nil
|
||||
}
|
||||
|
||||
func (fs *FileStorage) Save(messages []Message) error {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
fs.msgs = messages
|
||||
|
||||
data, err := json.Marshal(messages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(fs.path, data, 0o644)
|
||||
}
|
||||
|
||||
func (fs *FileStorage) Add(msg *cvms.ClientStreamMessage) error {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
fs.msgs = append(fs.msgs, Message{
|
||||
Message: msg,
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
data, err := json.Marshal(fs.msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(fs.path, data, 0o644)
|
||||
}
|
||||
|
||||
func (fs *FileStorage) Clear() error {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
fs.msgs = make([]Message, 0)
|
||||
return os.WriteFile(fs.path, []byte("[]"), 0o644)
|
||||
}
|
||||
+513
-224
File diff suppressed because it is too large
Load Diff
+28
-7
@@ -13,6 +13,15 @@ service Service {
|
||||
rpc Process(stream ClientStreamMessage) returns (stream ServerStreamMessage) {}
|
||||
}
|
||||
|
||||
message AgentStateReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message AgentStateRes {
|
||||
string id = 1;
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
message StopComputation {
|
||||
string computation_id = 1;
|
||||
}
|
||||
@@ -49,6 +58,8 @@ message ClientStreamMessage {
|
||||
AgentEvent agent_event = 2;
|
||||
RunResponse run_res = 3;
|
||||
StopComputationResponse stopComputationRes = 4;
|
||||
AgentStateRes agentStateRes = 5;
|
||||
AttestationResponse vTPMattestationReport = 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +68,15 @@ message ServerStreamMessage {
|
||||
RunReqChunks runReqChunks = 1;
|
||||
ComputationRunReq runReq = 2;
|
||||
StopComputation stopComputation = 3;
|
||||
AgentStateReq agentStateReq = 4;
|
||||
DisconnectReq disconnectReq = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message DisconnectReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message RunReqChunks {
|
||||
bytes data = 1;
|
||||
string id = 2;
|
||||
@@ -93,11 +110,15 @@ message Algorithm {
|
||||
|
||||
message AgentConfig {
|
||||
string port = 1;
|
||||
string host = 2;
|
||||
string cert_file = 3;
|
||||
string key_file = 4;
|
||||
string client_ca_file = 5;
|
||||
string server_ca_file = 6;
|
||||
string log_level = 7;
|
||||
bool attested_tls = 8;
|
||||
string cert_file = 2;
|
||||
string key_file = 3;
|
||||
string client_ca_file = 4;
|
||||
string server_ca_file = 5;
|
||||
string log_level = 6;
|
||||
bool attested_tls = 7;
|
||||
}
|
||||
|
||||
message AttestationResponse {
|
||||
bytes file = 1;
|
||||
string certSerialNumber = 2;
|
||||
}
|
||||
|
||||
+25
-13
@@ -8,12 +8,12 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
grpcserver "github.com/ultravioletrs/cocos/internal/server/grpc"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
)
|
||||
|
||||
type AgentServer interface {
|
||||
Start(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation) error
|
||||
Start(cfg agent.AgentConfig, cmp agent.Computation) error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
@@ -32,16 +32,24 @@ type agentServer struct {
|
||||
gs server.Server
|
||||
logger *slog.Logger
|
||||
svc agent.Service
|
||||
host string
|
||||
qp client.LeveledQuoteProvider
|
||||
caUrl string
|
||||
cvmId string
|
||||
}
|
||||
|
||||
func NewServer(logger *slog.Logger, svc agent.Service) AgentServer {
|
||||
func NewServer(logger *slog.Logger, svc agent.Service, host string, qp client.LeveledQuoteProvider, caUrl string, cvmId string) AgentServer {
|
||||
return &agentServer{
|
||||
logger: logger,
|
||||
svc: svc,
|
||||
host: host,
|
||||
qp: qp,
|
||||
caUrl: caUrl,
|
||||
cvmId: cvmId,
|
||||
}
|
||||
}
|
||||
|
||||
func (as *agentServer) Start(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation) error {
|
||||
func (as *agentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error {
|
||||
if cfg.Port == "" {
|
||||
cfg.Port = defSvcGRPCPort
|
||||
}
|
||||
@@ -49,7 +57,7 @@ func (as *agentServer) Start(ctx context.Context, cfg agent.AgentConfig, cmp age
|
||||
agentGrpcServerConfig := server.AgentConfig{
|
||||
ServerConfig: server.ServerConfig{
|
||||
BaseConfig: server.BaseConfig{
|
||||
Host: cfg.Host,
|
||||
Host: as.host,
|
||||
Port: cfg.Port,
|
||||
CertFile: cfg.CertFile,
|
||||
KeyFile: cfg.KeyFile,
|
||||
@@ -71,19 +79,23 @@ func (as *agentServer) Start(ctx context.Context, cfg agent.AgentConfig, cmp age
|
||||
return err
|
||||
}
|
||||
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
if err != nil {
|
||||
as.logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, as.qp, authSvc, as.caUrl, as.cvmId)
|
||||
|
||||
as.gs = grpcserver.New(ctx, cancel, svcName, agentGrpcServerConfig, registerAgentServiceServer, as.logger, qp, authSvc)
|
||||
go func() {
|
||||
err := as.gs.Start()
|
||||
if err != nil {
|
||||
as.logger.Error(fmt.Sprintf("failed to start grpc server %s", err.Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
return as.gs.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (as *agentServer) Stop() error {
|
||||
if as.gs == nil {
|
||||
return nil
|
||||
}
|
||||
return as.gs.Stop()
|
||||
}
|
||||
|
||||
@@ -1,42 +1,39 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
agent "github.com/ultravioletrs/cocos/agent"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
agent "github.com/ultravioletrs/cocos/agent"
|
||||
)
|
||||
|
||||
// AgentServerProvider is an autogenerated mock type for the AgentServerProvider type
|
||||
type AgentServerProvider struct {
|
||||
// AgentServer is an autogenerated mock type for the AgentServer type
|
||||
type AgentServer struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type AgentServerProvider_Expecter struct {
|
||||
type AgentServer_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AgentServerProvider) EXPECT() *AgentServerProvider_Expecter {
|
||||
return &AgentServerProvider_Expecter{mock: &_m.Mock}
|
||||
func (_m *AgentServer) EXPECT() *AgentServer_Expecter {
|
||||
return &AgentServer_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Start provides a mock function with given fields: ctx, cfg, cmp
|
||||
func (_m *AgentServerProvider) Start(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation) error {
|
||||
ret := _m.Called(ctx, cfg, cmp)
|
||||
// Start provides a mock function with given fields: cfg, cmp
|
||||
func (_m *AgentServer) Start(cfg agent.AgentConfig, cmp agent.Computation) error {
|
||||
ret := _m.Called(cfg, cmp)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Start")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, agent.AgentConfig, agent.Computation) error); ok {
|
||||
r0 = rf(ctx, cfg, cmp)
|
||||
if rf, ok := ret.Get(0).(func(agent.AgentConfig, agent.Computation) error); ok {
|
||||
r0 = rf(cfg, cmp)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
@@ -44,38 +41,37 @@ func (_m *AgentServerProvider) Start(ctx context.Context, cfg agent.AgentConfig,
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentServerProvider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
|
||||
type AgentServerProvider_Start_Call struct {
|
||||
// AgentServer_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
|
||||
type AgentServer_Start_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Start is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - cfg agent.AgentConfig
|
||||
// - cmp agent.Computation
|
||||
func (_e *AgentServerProvider_Expecter) Start(ctx interface{}, cfg interface{}, cmp interface{}) *AgentServerProvider_Start_Call {
|
||||
return &AgentServerProvider_Start_Call{Call: _e.mock.On("Start", ctx, cfg, cmp)}
|
||||
func (_e *AgentServer_Expecter) Start(cfg interface{}, cmp interface{}) *AgentServer_Start_Call {
|
||||
return &AgentServer_Start_Call{Call: _e.mock.On("Start", cfg, cmp)}
|
||||
}
|
||||
|
||||
func (_c *AgentServerProvider_Start_Call) Run(run func(ctx context.Context, cfg agent.AgentConfig, cmp agent.Computation)) *AgentServerProvider_Start_Call {
|
||||
func (_c *AgentServer_Start_Call) Run(run func(cfg agent.AgentConfig, cmp agent.Computation)) *AgentServer_Start_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(agent.AgentConfig), args[2].(agent.Computation))
|
||||
run(args[0].(agent.AgentConfig), args[1].(agent.Computation))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentServerProvider_Start_Call) Return(_a0 error) *AgentServerProvider_Start_Call {
|
||||
func (_c *AgentServer_Start_Call) Return(_a0 error) *AgentServer_Start_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentServerProvider_Start_Call) RunAndReturn(run func(context.Context, agent.AgentConfig, agent.Computation) error) *AgentServerProvider_Start_Call {
|
||||
func (_c *AgentServer_Start_Call) RunAndReturn(run func(agent.AgentConfig, agent.Computation) error) *AgentServer_Start_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Stop provides a mock function with given fields:
|
||||
func (_m *AgentServerProvider) Stop() error {
|
||||
// Stop provides a mock function with no fields
|
||||
func (_m *AgentServer) Stop() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -92,40 +88,40 @@ func (_m *AgentServerProvider) Stop() error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentServerProvider_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
|
||||
type AgentServerProvider_Stop_Call struct {
|
||||
// AgentServer_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
|
||||
type AgentServer_Stop_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Stop is a helper method to define mock.On call
|
||||
func (_e *AgentServerProvider_Expecter) Stop() *AgentServerProvider_Stop_Call {
|
||||
return &AgentServerProvider_Stop_Call{Call: _e.mock.On("Stop")}
|
||||
func (_e *AgentServer_Expecter) Stop() *AgentServer_Stop_Call {
|
||||
return &AgentServer_Stop_Call{Call: _e.mock.On("Stop")}
|
||||
}
|
||||
|
||||
func (_c *AgentServerProvider_Stop_Call) Run(run func()) *AgentServerProvider_Stop_Call {
|
||||
func (_c *AgentServer_Stop_Call) Run(run func()) *AgentServer_Stop_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentServerProvider_Stop_Call) Return(_a0 error) *AgentServerProvider_Stop_Call {
|
||||
func (_c *AgentServer_Stop_Call) Return(_a0 error) *AgentServer_Stop_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentServerProvider_Stop_Call) RunAndReturn(run func() error) *AgentServerProvider_Stop_Call {
|
||||
func (_c *AgentServer_Stop_Call) RunAndReturn(run func() error) *AgentServer_Stop_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewAgentServerProvider creates a new instance of AgentServerProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// NewAgentServer creates a new instance of AgentServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAgentServerProvider(t interface {
|
||||
func NewAgentServer(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AgentServerProvider {
|
||||
mock := &AgentServerProvider{}
|
||||
}) *AgentServer {
|
||||
mock := &AgentServer{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.0
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc v5.29.0
|
||||
// source: agent/events/events.proto
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -260,7 +261,7 @@ func (*EventsLogs_AgentEvent) isEventsLogs_Message() {}
|
||||
|
||||
var File_agent_events_events_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_agent_events_events_proto_rawDesc = []byte{
|
||||
var file_agent_events_events_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x19, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x65,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x65, 0x76, 0x65,
|
||||
0x6e, 0x74, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
@@ -299,16 +300,16 @@ var file_agent_events_events_proto_rawDesc = []byte{
|
||||
0x67, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
})
|
||||
|
||||
var (
|
||||
file_agent_events_events_proto_rawDescOnce sync.Once
|
||||
file_agent_events_events_proto_rawDescData = file_agent_events_events_proto_rawDesc
|
||||
file_agent_events_events_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_agent_events_events_proto_rawDescGZIP() []byte {
|
||||
file_agent_events_events_proto_rawDescOnce.Do(func() {
|
||||
file_agent_events_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_agent_events_events_proto_rawDescData)
|
||||
file_agent_events_events_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_agent_events_events_proto_rawDesc), len(file_agent_events_events_proto_rawDesc)))
|
||||
})
|
||||
return file_agent_events_events_proto_rawDescData
|
||||
}
|
||||
@@ -345,7 +346,7 @@ func file_agent_events_events_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_agent_events_events_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_events_events_proto_rawDesc), len(file_agent_events_events_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
@@ -356,7 +357,6 @@ func file_agent_events_events_proto_init() {
|
||||
MessageInfos: file_agent_events_events_proto_msgTypes,
|
||||
}.Build()
|
||||
File_agent_events_events_proto = out.File
|
||||
file_agent_events_events_proto_rawDesc = nil
|
||||
file_agent_events_events_proto_goTypes = nil
|
||||
file_agent_events_events_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -56,7 +56,7 @@ func (_c *Service_SendEvent_Call) Return() *Service_SendEvent_Call {
|
||||
}
|
||||
|
||||
func (_c *Service_SendEvent_Call) RunAndReturn(run func(string, string, string, json.RawMessage)) *Service_SendEvent_Call {
|
||||
_c.Call.Return(run)
|
||||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
||||
+65
-17
@@ -1,14 +1,15 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
agent "github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
@@ -73,9 +74,9 @@ func (_c *Service_Algo_Call) RunAndReturn(run func(context.Context, agent.Algori
|
||||
return _c
|
||||
}
|
||||
|
||||
// Attestation provides a mock function with given fields: ctx, reportData
|
||||
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte, error) {
|
||||
ret := _m.Called(ctx, reportData)
|
||||
// Attestation provides a mock function with given fields: ctx, reportData, nonce, attType
|
||||
func (_m *Service) Attestation(ctx context.Context, reportData [64]byte, nonce [32]byte, attType config.AttestationType) ([]byte, error) {
|
||||
ret := _m.Called(ctx, reportData, nonce, attType)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Attestation")
|
||||
@@ -83,19 +84,19 @@ func (_m *Service) Attestation(ctx context.Context, reportData [64]byte) ([]byte
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) ([]byte, error)); ok {
|
||||
return rf(ctx, reportData)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)); ok {
|
||||
return rf(ctx, reportData, nonce, attType)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte) []byte); ok {
|
||||
r0 = rf(ctx, reportData)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, [64]byte, [32]byte, config.AttestationType) []byte); ok {
|
||||
r0 = rf(ctx, reportData, nonce, attType)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [64]byte) error); ok {
|
||||
r1 = rf(ctx, reportData)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, [64]byte, [32]byte, config.AttestationType) error); ok {
|
||||
r1 = rf(ctx, reportData, nonce, attType)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -111,13 +112,15 @@ type Service_Attestation_Call struct {
|
||||
// Attestation is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - reportData [64]byte
|
||||
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}) *Service_Attestation_Call {
|
||||
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData)}
|
||||
// - nonce [32]byte
|
||||
// - attType config.AttestationType
|
||||
func (_e *Service_Expecter) Attestation(ctx interface{}, reportData interface{}, nonce interface{}, attType interface{}) *Service_Attestation_Call {
|
||||
return &Service_Attestation_Call{Call: _e.mock.On("Attestation", ctx, reportData, nonce, attType)}
|
||||
}
|
||||
|
||||
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte)) *Service_Attestation_Call {
|
||||
func (_c *Service_Attestation_Call) Run(run func(ctx context.Context, reportData [64]byte, nonce [32]byte, attType config.AttestationType)) *Service_Attestation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([64]byte))
|
||||
run(args[0].(context.Context), args[1].([64]byte), args[2].([32]byte), args[3].(config.AttestationType))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -127,7 +130,7 @@ func (_c *Service_Attestation_Call) Return(_a0 []byte, _a1 error) *Service_Attes
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte) ([]byte, error)) *Service_Attestation_Call {
|
||||
func (_c *Service_Attestation_Call) RunAndReturn(run func(context.Context, [64]byte, [32]byte, config.AttestationType) ([]byte, error)) *Service_Attestation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -284,6 +287,51 @@ func (_c *Service_Result_Call) RunAndReturn(run func(context.Context) ([]byte, e
|
||||
return _c
|
||||
}
|
||||
|
||||
// State provides a mock function with no fields
|
||||
func (_m *Service) State() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for State")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Service_State_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'State'
|
||||
type Service_State_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// State is a helper method to define mock.On call
|
||||
func (_e *Service_Expecter) State() *Service_State_Call {
|
||||
return &Service_State_Call{Call: _e.mock.On("State")}
|
||||
}
|
||||
|
||||
func (_c *Service_State_Call) Run(run func()) *Service_State_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_State_Call) Return(_a0 string) *Service_State_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_State_Call) RunAndReturn(run func() string) *Service_State_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// StopComputation provides a mock function with given fields: ctx
|
||||
func (_m *Service) StopComputation(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
+246
-19
@@ -1,8 +1,8 @@
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
@@ -16,12 +16,20 @@ import (
|
||||
)
|
||||
|
||||
// AgentService_AlgoClient is an autogenerated mock type for the AgentService_AlgoClient type
|
||||
type AgentService_AlgoClient struct {
|
||||
type AgentService_AlgoClient[Req interface{}, Res interface{}] struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CloseAndRecv provides a mock function with given fields:
|
||||
func (_m *AgentService_AlgoClient) CloseAndRecv() (*agent.AlgoResponse, error) {
|
||||
type AgentService_AlgoClient_Expecter[Req interface{}, Res interface{}] struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) EXPECT() *AgentService_AlgoClient_Expecter[Req, Res] {
|
||||
return &AgentService_AlgoClient_Expecter[Req, Res]{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// CloseAndRecv provides a mock function with no fields
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) CloseAndRecv() (*agent.AlgoResponse, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -50,8 +58,35 @@ func (_m *AgentService_AlgoClient) CloseAndRecv() (*agent.AlgoResponse, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CloseSend provides a mock function with given fields:
|
||||
func (_m *AgentService_AlgoClient) CloseSend() error {
|
||||
// AgentService_AlgoClient_CloseAndRecv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseAndRecv'
|
||||
type AgentService_AlgoClient_CloseAndRecv_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CloseAndRecv is a helper method to define mock.On call
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) CloseAndRecv() *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]{Call: _e.mock.On("CloseAndRecv")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]) Return(_a0 *agent.AlgoResponse, _a1 error) *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res]) RunAndReturn(run func() (*agent.AlgoResponse, error)) *AgentService_AlgoClient_CloseAndRecv_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CloseSend provides a mock function with no fields
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) CloseSend() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -68,8 +103,35 @@ func (_m *AgentService_AlgoClient) CloseSend() error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Context provides a mock function with given fields:
|
||||
func (_m *AgentService_AlgoClient) Context() context.Context {
|
||||
// AgentService_AlgoClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'
|
||||
type AgentService_AlgoClient_CloseSend_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CloseSend is a helper method to define mock.On call
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) CloseSend() *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_CloseSend_Call[Req, Res]{Call: _e.mock.On("CloseSend")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_CloseSend_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_CloseSend_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_CloseSend_Call[Req, Res]) RunAndReturn(run func() error) *AgentService_AlgoClient_CloseSend_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Context provides a mock function with no fields
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) Context() context.Context {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -88,8 +150,35 @@ func (_m *AgentService_AlgoClient) Context() context.Context {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Header provides a mock function with given fields:
|
||||
func (_m *AgentService_AlgoClient) Header() (metadata.MD, error) {
|
||||
// AgentService_AlgoClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'
|
||||
type AgentService_AlgoClient_Context_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Context is a helper method to define mock.On call
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Context() *AgentService_AlgoClient_Context_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_Context_Call[Req, Res]{Call: _e.mock.On("Context")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Context_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_Context_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Context_Call[Req, Res]) Return(_a0 context.Context) *AgentService_AlgoClient_Context_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Context_Call[Req, Res]) RunAndReturn(run func() context.Context) *AgentService_AlgoClient_Context_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Header provides a mock function with no fields
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) Header() (metadata.MD, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -118,8 +207,35 @@ func (_m *AgentService_AlgoClient) Header() (metadata.MD, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AgentService_AlgoClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'
|
||||
type AgentService_AlgoClient_Header_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Header is a helper method to define mock.On call
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Header() *AgentService_AlgoClient_Header_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_Header_Call[Req, Res]{Call: _e.mock.On("Header")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Header_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_Header_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Header_Call[Req, Res]) Return(_a0 metadata.MD, _a1 error) *AgentService_AlgoClient_Header_Call[Req, Res] {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Header_Call[Req, Res]) RunAndReturn(run func() (metadata.MD, error)) *AgentService_AlgoClient_Header_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RecvMsg provides a mock function with given fields: m
|
||||
func (_m *AgentService_AlgoClient) RecvMsg(m interface{}) error {
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) RecvMsg(m interface{}) error {
|
||||
ret := _m.Called(m)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -136,8 +252,36 @@ func (_m *AgentService_AlgoClient) RecvMsg(m interface{}) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentService_AlgoClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'
|
||||
type AgentService_AlgoClient_RecvMsg_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RecvMsg is a helper method to define mock.On call
|
||||
// - m interface{}
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) RecvMsg(m interface{}) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_RecvMsg_Call[Req, Res]{Call: _e.mock.On("RecvMsg", m)}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_RecvMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_RecvMsg_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_RecvMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_AlgoClient_RecvMsg_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Send provides a mock function with given fields: _a0
|
||||
func (_m *AgentService_AlgoClient) Send(_a0 *agent.AlgoRequest) error {
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) Send(_a0 *agent.AlgoRequest) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -154,8 +298,36 @@ func (_m *AgentService_AlgoClient) Send(_a0 *agent.AlgoRequest) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentService_AlgoClient_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'
|
||||
type AgentService_AlgoClient_Send_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Send is a helper method to define mock.On call
|
||||
// - _a0 *agent.AlgoRequest
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Send(_a0 interface{}) *AgentService_AlgoClient_Send_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_Send_Call[Req, Res]{Call: _e.mock.On("Send", _a0)}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Send_Call[Req, Res]) Run(run func(_a0 *agent.AlgoRequest)) *AgentService_AlgoClient_Send_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*agent.AlgoRequest))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Send_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_Send_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Send_Call[Req, Res]) RunAndReturn(run func(*agent.AlgoRequest) error) *AgentService_AlgoClient_Send_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SendMsg provides a mock function with given fields: m
|
||||
func (_m *AgentService_AlgoClient) SendMsg(m interface{}) error {
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) SendMsg(m interface{}) error {
|
||||
ret := _m.Called(m)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -172,8 +344,36 @@ func (_m *AgentService_AlgoClient) SendMsg(m interface{}) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Trailer provides a mock function with given fields:
|
||||
func (_m *AgentService_AlgoClient) Trailer() metadata.MD {
|
||||
// AgentService_AlgoClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'
|
||||
type AgentService_AlgoClient_SendMsg_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SendMsg is a helper method to define mock.On call
|
||||
// - m interface{}
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) SendMsg(m interface{}) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_SendMsg_Call[Req, Res]{Call: _e.mock.On("SendMsg", m)}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_SendMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_SendMsg_Call[Req, Res]) Return(_a0 error) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_SendMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_AlgoClient_SendMsg_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Trailer provides a mock function with no fields
|
||||
func (_m *AgentService_AlgoClient[Req, Res]) Trailer() metadata.MD {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -192,13 +392,40 @@ func (_m *AgentService_AlgoClient) Trailer() metadata.MD {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentService_AlgoClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'
|
||||
type AgentService_AlgoClient_Trailer_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Trailer is a helper method to define mock.On call
|
||||
func (_e *AgentService_AlgoClient_Expecter[Req, Res]) Trailer() *AgentService_AlgoClient_Trailer_Call[Req, Res] {
|
||||
return &AgentService_AlgoClient_Trailer_Call[Req, Res]{Call: _e.mock.On("Trailer")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Trailer_Call[Req, Res]) Run(run func()) *AgentService_AlgoClient_Trailer_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Trailer_Call[Req, Res]) Return(_a0 metadata.MD) *AgentService_AlgoClient_Trailer_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_AlgoClient_Trailer_Call[Req, Res]) RunAndReturn(run func() metadata.MD) *AgentService_AlgoClient_Trailer_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewAgentService_AlgoClient creates a new instance of AgentService_AlgoClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAgentService_AlgoClient(t interface {
|
||||
func NewAgentService_AlgoClient[Req interface{}, Res interface{}](t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AgentService_AlgoClient {
|
||||
mock := &AgentService_AlgoClient{}
|
||||
}) *AgentService_AlgoClient[Req, Res] {
|
||||
mock := &AgentService_AlgoClient[Req, Res]{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
+246
-19
@@ -1,8 +1,8 @@
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
@@ -16,12 +16,20 @@ import (
|
||||
)
|
||||
|
||||
// AgentService_DataClient is an autogenerated mock type for the AgentService_DataClient type
|
||||
type AgentService_DataClient struct {
|
||||
type AgentService_DataClient[Req interface{}, Res interface{}] struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CloseAndRecv provides a mock function with given fields:
|
||||
func (_m *AgentService_DataClient) CloseAndRecv() (*agent.DataResponse, error) {
|
||||
type AgentService_DataClient_Expecter[Req interface{}, Res interface{}] struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *AgentService_DataClient[Req, Res]) EXPECT() *AgentService_DataClient_Expecter[Req, Res] {
|
||||
return &AgentService_DataClient_Expecter[Req, Res]{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// CloseAndRecv provides a mock function with no fields
|
||||
func (_m *AgentService_DataClient[Req, Res]) CloseAndRecv() (*agent.DataResponse, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -50,8 +58,35 @@ func (_m *AgentService_DataClient) CloseAndRecv() (*agent.DataResponse, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CloseSend provides a mock function with given fields:
|
||||
func (_m *AgentService_DataClient) CloseSend() error {
|
||||
// AgentService_DataClient_CloseAndRecv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseAndRecv'
|
||||
type AgentService_DataClient_CloseAndRecv_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CloseAndRecv is a helper method to define mock.On call
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) CloseAndRecv() *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
|
||||
return &AgentService_DataClient_CloseAndRecv_Call[Req, Res]{Call: _e.mock.On("CloseAndRecv")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_CloseAndRecv_Call[Req, Res]) Run(run func()) *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_CloseAndRecv_Call[Req, Res]) Return(_a0 *agent.DataResponse, _a1 error) *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_CloseAndRecv_Call[Req, Res]) RunAndReturn(run func() (*agent.DataResponse, error)) *AgentService_DataClient_CloseAndRecv_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CloseSend provides a mock function with no fields
|
||||
func (_m *AgentService_DataClient[Req, Res]) CloseSend() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -68,8 +103,35 @@ func (_m *AgentService_DataClient) CloseSend() error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Context provides a mock function with given fields:
|
||||
func (_m *AgentService_DataClient) Context() context.Context {
|
||||
// AgentService_DataClient_CloseSend_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CloseSend'
|
||||
type AgentService_DataClient_CloseSend_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CloseSend is a helper method to define mock.On call
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) CloseSend() *AgentService_DataClient_CloseSend_Call[Req, Res] {
|
||||
return &AgentService_DataClient_CloseSend_Call[Req, Res]{Call: _e.mock.On("CloseSend")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_CloseSend_Call[Req, Res]) Run(run func()) *AgentService_DataClient_CloseSend_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_CloseSend_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_CloseSend_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_CloseSend_Call[Req, Res]) RunAndReturn(run func() error) *AgentService_DataClient_CloseSend_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Context provides a mock function with no fields
|
||||
func (_m *AgentService_DataClient[Req, Res]) Context() context.Context {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -88,8 +150,35 @@ func (_m *AgentService_DataClient) Context() context.Context {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Header provides a mock function with given fields:
|
||||
func (_m *AgentService_DataClient) Header() (metadata.MD, error) {
|
||||
// AgentService_DataClient_Context_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Context'
|
||||
type AgentService_DataClient_Context_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Context is a helper method to define mock.On call
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) Context() *AgentService_DataClient_Context_Call[Req, Res] {
|
||||
return &AgentService_DataClient_Context_Call[Req, Res]{Call: _e.mock.On("Context")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Context_Call[Req, Res]) Run(run func()) *AgentService_DataClient_Context_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Context_Call[Req, Res]) Return(_a0 context.Context) *AgentService_DataClient_Context_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Context_Call[Req, Res]) RunAndReturn(run func() context.Context) *AgentService_DataClient_Context_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Header provides a mock function with no fields
|
||||
func (_m *AgentService_DataClient[Req, Res]) Header() (metadata.MD, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -118,8 +207,35 @@ func (_m *AgentService_DataClient) Header() (metadata.MD, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AgentService_DataClient_Header_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Header'
|
||||
type AgentService_DataClient_Header_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Header is a helper method to define mock.On call
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) Header() *AgentService_DataClient_Header_Call[Req, Res] {
|
||||
return &AgentService_DataClient_Header_Call[Req, Res]{Call: _e.mock.On("Header")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Header_Call[Req, Res]) Run(run func()) *AgentService_DataClient_Header_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Header_Call[Req, Res]) Return(_a0 metadata.MD, _a1 error) *AgentService_DataClient_Header_Call[Req, Res] {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Header_Call[Req, Res]) RunAndReturn(run func() (metadata.MD, error)) *AgentService_DataClient_Header_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RecvMsg provides a mock function with given fields: m
|
||||
func (_m *AgentService_DataClient) RecvMsg(m interface{}) error {
|
||||
func (_m *AgentService_DataClient[Req, Res]) RecvMsg(m interface{}) error {
|
||||
ret := _m.Called(m)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -136,8 +252,36 @@ func (_m *AgentService_DataClient) RecvMsg(m interface{}) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentService_DataClient_RecvMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RecvMsg'
|
||||
type AgentService_DataClient_RecvMsg_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RecvMsg is a helper method to define mock.On call
|
||||
// - m interface{}
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) RecvMsg(m interface{}) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
|
||||
return &AgentService_DataClient_RecvMsg_Call[Req, Res]{Call: _e.mock.On("RecvMsg", m)}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_RecvMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_RecvMsg_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_RecvMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_DataClient_RecvMsg_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Send provides a mock function with given fields: _a0
|
||||
func (_m *AgentService_DataClient) Send(_a0 *agent.DataRequest) error {
|
||||
func (_m *AgentService_DataClient[Req, Res]) Send(_a0 *agent.DataRequest) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -154,8 +298,36 @@ func (_m *AgentService_DataClient) Send(_a0 *agent.DataRequest) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentService_DataClient_Send_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Send'
|
||||
type AgentService_DataClient_Send_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Send is a helper method to define mock.On call
|
||||
// - _a0 *agent.DataRequest
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) Send(_a0 interface{}) *AgentService_DataClient_Send_Call[Req, Res] {
|
||||
return &AgentService_DataClient_Send_Call[Req, Res]{Call: _e.mock.On("Send", _a0)}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Send_Call[Req, Res]) Run(run func(_a0 *agent.DataRequest)) *AgentService_DataClient_Send_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*agent.DataRequest))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Send_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_Send_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Send_Call[Req, Res]) RunAndReturn(run func(*agent.DataRequest) error) *AgentService_DataClient_Send_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SendMsg provides a mock function with given fields: m
|
||||
func (_m *AgentService_DataClient) SendMsg(m interface{}) error {
|
||||
func (_m *AgentService_DataClient[Req, Res]) SendMsg(m interface{}) error {
|
||||
ret := _m.Called(m)
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -172,8 +344,36 @@ func (_m *AgentService_DataClient) SendMsg(m interface{}) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Trailer provides a mock function with given fields:
|
||||
func (_m *AgentService_DataClient) Trailer() metadata.MD {
|
||||
// AgentService_DataClient_SendMsg_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMsg'
|
||||
type AgentService_DataClient_SendMsg_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SendMsg is a helper method to define mock.On call
|
||||
// - m interface{}
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) SendMsg(m interface{}) *AgentService_DataClient_SendMsg_Call[Req, Res] {
|
||||
return &AgentService_DataClient_SendMsg_Call[Req, Res]{Call: _e.mock.On("SendMsg", m)}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_SendMsg_Call[Req, Res]) Run(run func(m interface{})) *AgentService_DataClient_SendMsg_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(interface{}))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_SendMsg_Call[Req, Res]) Return(_a0 error) *AgentService_DataClient_SendMsg_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_SendMsg_Call[Req, Res]) RunAndReturn(run func(interface{}) error) *AgentService_DataClient_SendMsg_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Trailer provides a mock function with no fields
|
||||
func (_m *AgentService_DataClient[Req, Res]) Trailer() metadata.MD {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -192,13 +392,40 @@ func (_m *AgentService_DataClient) Trailer() metadata.MD {
|
||||
return r0
|
||||
}
|
||||
|
||||
// AgentService_DataClient_Trailer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trailer'
|
||||
type AgentService_DataClient_Trailer_Call[Req interface{}, Res interface{}] struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Trailer is a helper method to define mock.On call
|
||||
func (_e *AgentService_DataClient_Expecter[Req, Res]) Trailer() *AgentService_DataClient_Trailer_Call[Req, Res] {
|
||||
return &AgentService_DataClient_Trailer_Call[Req, Res]{Call: _e.mock.On("Trailer")}
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Trailer_Call[Req, Res]) Run(run func()) *AgentService_DataClient_Trailer_Call[Req, Res] {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Trailer_Call[Req, Res]) Return(_a0 metadata.MD) *AgentService_DataClient_Trailer_Call[Req, Res] {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *AgentService_DataClient_Trailer_Call[Req, Res]) RunAndReturn(run func() metadata.MD) *AgentService_DataClient_Trailer_Call[Req, Res] {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewAgentService_DataClient creates a new instance of AgentService_DataClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewAgentService_DataClient(t interface {
|
||||
func NewAgentService_DataClient[Req interface{}, Res interface{}](t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *AgentService_DataClient {
|
||||
mock := &AgentService_DataClient{}
|
||||
}) *AgentService_DataClient[Req, Res] {
|
||||
mock := &AgentService_DataClient[Req, Res]{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
+67
-26
@@ -23,6 +23,9 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
"github.com/ultravioletrs/cocos/agent/statemachine"
|
||||
"github.com/ultravioletrs/cocos/internal"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -69,8 +72,6 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
// ReportDataSize is the size of the report data expected by the attestation service.
|
||||
ReportDataSize = 64
|
||||
algoFilePermission = 0o700
|
||||
)
|
||||
|
||||
@@ -99,6 +100,8 @@ var (
|
||||
ErrAllResultsConsumed = errors.New("all results have been consumed by declared consumers")
|
||||
// ErrAttestationFailed attestation failed.
|
||||
ErrAttestationFailed = errors.New("failed to get raw quote")
|
||||
// ErrAttType indicates that the attestation type that is requested does not exist or is not supported.
|
||||
ErrAttestationType = errors.New("attestation type does not exist or is not supported")
|
||||
)
|
||||
|
||||
// Service specifies an API that must be fullfiled by the domain service
|
||||
@@ -109,27 +112,30 @@ type Service interface {
|
||||
Algo(ctx context.Context, algorithm Algorithm) error
|
||||
Data(ctx context.Context, dataset Dataset) error
|
||||
Result(ctx context.Context) ([]byte, error)
|
||||
Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error)
|
||||
Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error)
|
||||
State() string
|
||||
}
|
||||
|
||||
type agentService struct {
|
||||
mu sync.Mutex
|
||||
computation Computation // Holds the current computation request details.
|
||||
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
|
||||
result []byte // Stores the result of the computation.
|
||||
sm statemachine.StateMachine // Manages the state transitions of the agent service.
|
||||
runError error // Stores any error encountered during the computation run.
|
||||
eventSvc events.Service // Service for publishing events related to computation.
|
||||
quoteProvider client.QuoteProvider // Provider for generating attestation quotes.
|
||||
logger *slog.Logger // Logger for the agent service.
|
||||
resultsConsumed bool // Indicates if the results have been consumed.
|
||||
cancel context.CancelFunc // Cancels the computation context.
|
||||
computation Computation // Holds the current computation request details.
|
||||
algorithm algorithm.Algorithm // Filepath to the algorithm received for the computation.
|
||||
result []byte // Stores the result of the computation.
|
||||
sm statemachine.StateMachine // Manages the state transitions of the agent service.
|
||||
runError error // Stores any error encountered during the computation run.
|
||||
eventSvc events.Service // Service for publishing events related to computation.
|
||||
quoteProvider client.LeveledQuoteProvider // Provider for generating attestation quotes.
|
||||
logger *slog.Logger // Logger for the agent service.
|
||||
resultsConsumed bool // Indicates if the results have been consumed.
|
||||
cancel context.CancelFunc // Cancels the computation context.
|
||||
vmpl int // VMPL at which the Agent is running.
|
||||
vtpmAttest vtpm.VtpmAttest // Attestation function.
|
||||
}
|
||||
|
||||
var _ Service = (*agentService)(nil)
|
||||
|
||||
// New instantiates the agent service implementation.
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quoteProvider client.QuoteProvider) Service {
|
||||
func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quoteProvider client.LeveledQuoteProvider, vmlp int, vtpmAttest vtpm.VtpmAttest) Service {
|
||||
sm := statemachine.NewStateMachine(Idle)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
svc := &agentService{
|
||||
@@ -138,6 +144,8 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quot
|
||||
quoteProvider: quoteProvider,
|
||||
logger: logger,
|
||||
cancel: cancel,
|
||||
vmpl: vmlp,
|
||||
vtpmAttest: vtpmAttest,
|
||||
}
|
||||
|
||||
transitions := []statemachine.Transition{
|
||||
@@ -172,11 +180,15 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, quot
|
||||
return svc
|
||||
}
|
||||
|
||||
func (as *agentService) State() string {
|
||||
return as.sm.GetState().String()
|
||||
}
|
||||
|
||||
func (as *agentService) InitComputation(ctx context.Context, cmp Computation) error {
|
||||
defer as.sm.SendEvent(ManifestReceived)
|
||||
if as.sm.GetState() != ReceivingManifest {
|
||||
return ErrStateNotReady
|
||||
}
|
||||
defer as.sm.SendEvent(ManifestReceived)
|
||||
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
@@ -203,14 +215,16 @@ func (as *agentService) StopComputation(ctx context.Context) error {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
as.eventSvc.SendEvent(as.computation.ID, "Stopped", "Stopped", json.RawMessage{})
|
||||
|
||||
as.cancel()
|
||||
|
||||
if err := as.algorithm.Stop(); err != nil {
|
||||
return fmt.Errorf("error stopping computation: %v", err)
|
||||
if as.algorithm != nil {
|
||||
if err := as.algorithm.Stop(); err != nil {
|
||||
return fmt.Errorf("error stopping computation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sm := statemachine.NewStateMachine(Idle)
|
||||
|
||||
if err := os.RemoveAll(algorithm.DatasetsDir); err != nil {
|
||||
return fmt.Errorf("error removing datasets directory: %v", err)
|
||||
}
|
||||
@@ -219,13 +233,24 @@ func (as *agentService) StopComputation(ctx context.Context) error {
|
||||
return fmt.Errorf("error removing results directory: %v", err)
|
||||
}
|
||||
|
||||
as.sm = sm
|
||||
as.sm.Reset(Idle)
|
||||
|
||||
as.computation = Computation{}
|
||||
as.algorithm = nil
|
||||
as.result = nil
|
||||
as.runError = nil
|
||||
as.resultsConsumed = false
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
as.cancel = cancel
|
||||
|
||||
go func() {
|
||||
if err := as.sm.Start(ctx); err != nil {
|
||||
as.logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
as.sm.SendEvent(Start)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -392,13 +417,29 @@ func (as *agentService) Result(ctx context.Context) ([]byte, error) {
|
||||
return as.result, as.runError
|
||||
}
|
||||
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [ReportDataSize]byte) ([]byte, error) {
|
||||
rawQuote, err := as.quoteProvider.GetRawQuote(reportData)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
func (as *agentService) Attestation(ctx context.Context, reportData [quoteprovider.Nonce]byte, nonce [vtpm.Nonce]byte, attType config.AttestationType) ([]byte, error) {
|
||||
switch attType {
|
||||
case config.SNP:
|
||||
rawQuote, err := as.quoteProvider.GetRawQuoteAtLevel(reportData, uint(as.vmpl))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return rawQuote, nil
|
||||
case config.VTPM:
|
||||
vTPMQuote, err := as.vtpmAttest(reportData[:], nonce[:], false)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
case config.SNPvTPM:
|
||||
vTPMQuote, err := as.vtpmAttest(reportData[:], nonce[:], true)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return vTPMQuote, nil
|
||||
default:
|
||||
return []byte{}, ErrAttestationType
|
||||
}
|
||||
|
||||
return rawQuote, nil
|
||||
}
|
||||
|
||||
func (as *agentService) runComputation(state statemachine.State) {
|
||||
|
||||
+17
-13
@@ -22,6 +22,7 @@ import (
|
||||
smmocks "github.com/ultravioletrs/cocos/agent/statemachine/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
mocks2 "github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
@@ -35,7 +36,7 @@ var (
|
||||
const datasetFile = "iris.csv"
|
||||
|
||||
func TestAlgo(t *testing.T) {
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
@@ -120,7 +121,7 @@ func TestAlgo(t *testing.T) {
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
svc := New(ctx, mglog.NewMock(), events, qp)
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
|
||||
|
||||
err := svc.InitComputation(ctx, testComputation(t))
|
||||
require.NoError(t, err)
|
||||
@@ -139,7 +140,7 @@ func TestAlgo(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestData(t *testing.T) {
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
algo, err := os.ReadFile(algoPath)
|
||||
@@ -215,7 +216,7 @@ func TestData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, qp)
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
|
||||
|
||||
err := svc.InitComputation(ctx, testComputation(t))
|
||||
require.NoError(t, err)
|
||||
@@ -240,7 +241,7 @@ func TestData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResult(t *testing.T) {
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
qp, err := quoteprovider.GetLeveledQuoteProvider()
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
@@ -323,23 +324,26 @@ func TestResult(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAttestation(t *testing.T) {
|
||||
qp := new(mocks2.QuoteProvider)
|
||||
qp := new(mocks2.LeveledQuoteProvider)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
reportData [ReportDataSize]byte
|
||||
reportData [quoteprovider.Nonce]byte
|
||||
nonce [vtpm.Nonce]byte
|
||||
rawQuote []uint8
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Test attestation successful",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: make([]uint8, 0),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "Test attestation failed",
|
||||
reportData: generateReportData(),
|
||||
nonce: [32]byte{},
|
||||
rawQuote: nil,
|
||||
err: ErrAttestationFailed,
|
||||
},
|
||||
@@ -355,22 +359,22 @@ func TestAttestation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
getQuote := qp.On("GetRawQuote", mock.Anything).Return(tc.rawQuote, tc.err)
|
||||
getQuote := qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.rawQuote, tc.err)
|
||||
if tc.err != ErrAttestationFailed {
|
||||
getQuote = qp.On("GetRawQuote", mock.Anything).Return(tc.reportData, nil)
|
||||
getQuote = qp.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return(tc.nonce, nil)
|
||||
}
|
||||
defer getQuote.Unset()
|
||||
|
||||
svc := New(ctx, mglog.NewMock(), events, qp)
|
||||
svc := New(ctx, mglog.NewMock(), events, qp, 0, vtpm.EmptyAttest)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
_, err := svc.Attestation(ctx, tc.reportData)
|
||||
_, err := svc.Attestation(ctx, tc.reportData, tc.nonce, 0)
|
||||
assert.True(t, errors.Contains(err, tc.err), "expected %v, got %v", tc.err, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateReportData() [ReportDataSize]byte {
|
||||
bytes := make([]byte, ReportDataSize)
|
||||
func generateReportData() [quoteprovider.Nonce]byte {
|
||||
bytes := make([]byte, quoteprovider.Nonce)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate random bytes: %v", err)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -54,11 +54,11 @@ func (_c *StateMachine_AddTransition_Call) Return() *StateMachine_AddTransition_
|
||||
}
|
||||
|
||||
func (_c *StateMachine_AddTransition_Call) RunAndReturn(run func(statemachine.Transition)) *StateMachine_AddTransition_Call {
|
||||
_c.Call.Return(run)
|
||||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetState provides a mock function with given fields:
|
||||
// GetState provides a mock function with no fields
|
||||
func (_m *StateMachine) GetState() statemachine.State {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -105,6 +105,39 @@ func (_c *StateMachine_GetState_Call) RunAndReturn(run func() statemachine.State
|
||||
return _c
|
||||
}
|
||||
|
||||
// Reset provides a mock function with given fields: initialState
|
||||
func (_m *StateMachine) Reset(initialState statemachine.State) {
|
||||
_m.Called(initialState)
|
||||
}
|
||||
|
||||
// StateMachine_Reset_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reset'
|
||||
type StateMachine_Reset_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Reset is a helper method to define mock.On call
|
||||
// - initialState statemachine.State
|
||||
func (_e *StateMachine_Expecter) Reset(initialState interface{}) *StateMachine_Reset_Call {
|
||||
return &StateMachine_Reset_Call{Call: _e.mock.On("Reset", initialState)}
|
||||
}
|
||||
|
||||
func (_c *StateMachine_Reset_Call) Run(run func(initialState statemachine.State)) *StateMachine_Reset_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(statemachine.State))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *StateMachine_Reset_Call) Return() *StateMachine_Reset_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *StateMachine_Reset_Call) RunAndReturn(run func(statemachine.State)) *StateMachine_Reset_Call {
|
||||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SendEvent provides a mock function with given fields: event
|
||||
func (_m *StateMachine) SendEvent(event statemachine.Event) {
|
||||
_m.Called(event)
|
||||
@@ -134,7 +167,7 @@ func (_c *StateMachine_SendEvent_Call) Return() *StateMachine_SendEvent_Call {
|
||||
}
|
||||
|
||||
func (_c *StateMachine_SendEvent_Call) RunAndReturn(run func(statemachine.Event)) *StateMachine_SendEvent_Call {
|
||||
_c.Call.Return(run)
|
||||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -168,7 +201,7 @@ func (_c *StateMachine_SetAction_Call) Return() *StateMachine_SetAction_Call {
|
||||
}
|
||||
|
||||
func (_c *StateMachine_SetAction_Call) RunAndReturn(run func(statemachine.State, statemachine.Action)) *StateMachine_SetAction_Call {
|
||||
_c.Call.Return(run)
|
||||
_c.Run(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ type StateMachine interface {
|
||||
GetState() State
|
||||
SendEvent(event Event)
|
||||
Start(ctx context.Context) error
|
||||
Reset(initialState State)
|
||||
}
|
||||
|
||||
type stateMachine struct {
|
||||
@@ -89,6 +90,20 @@ func (sm *stateMachine) Start(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *stateMachine) Reset(initialState State) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
// Reset current state to initial state
|
||||
sm.currentState = initialState
|
||||
|
||||
// Close the existing event channel to stop processing events
|
||||
close(sm.eventChan)
|
||||
|
||||
// Create a new event channel
|
||||
sm.eventChan = make(chan Event)
|
||||
}
|
||||
|
||||
func (sm *stateMachine) handleEvent(event Event) error {
|
||||
sm.mu.Lock()
|
||||
currentState := sm.currentState
|
||||
|
||||
Binary file not shown.
@@ -100,3 +100,22 @@ When defining the manifest dataset and algorithm checksums are required. This ca
|
||||
```bash
|
||||
./build/cocos-cli checksum <path_to_dataset_or_algorithm>
|
||||
```
|
||||
|
||||
#### Measure IGVM file
|
||||
We assume that our current working directory is the root of the cocos repository, both on the host machine and in the VM.
|
||||
|
||||
`igvmmeasure` calculates the launch measurement for an IGVM file and can generate a signed version. It ensures integrity by precomputing the expected launch digest, which can be verified against the attestation report. The tool parses IGVM directives, outputs the measurement as a hex string, or creates a signed file for verification at guest launch.
|
||||
|
||||
##### Example
|
||||
We measure an IGVM file using our measure command, run:
|
||||
|
||||
```bash
|
||||
./build/cocos-cli igvmmeasure /path/to/igvm/file
|
||||
```
|
||||
|
||||
The tool will parse the directives in the IGVM file, calculate the launch measurement, and output the computed digest. If successful, it prints the measurement to standard output.
|
||||
|
||||
Here is a sample output
|
||||
```
|
||||
91c4929bec2d0ecf11a708e09f0a57d7d82208bcba2451564444a4b01c22d047995ca27f9053f86de4e8063e9f810548
|
||||
```
|
||||
+438
-87
@@ -3,9 +3,11 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -17,11 +19,17 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/server"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
@@ -42,9 +50,12 @@ const (
|
||||
size48 = 48
|
||||
size64 = 64
|
||||
attestationFilePath = "attestation.bin"
|
||||
vtpmFilePath = "../quote.dat"
|
||||
attestationJson = "attestation.json"
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
FormatBinaryPB = "binarypb"
|
||||
FormatTextProto = "textproto"
|
||||
exampleJSONConfig = `
|
||||
{
|
||||
"rootOfTrust":{
|
||||
@@ -98,29 +109,47 @@ const (
|
||||
}
|
||||
}
|
||||
`
|
||||
SNP = "snp"
|
||||
VTPM = "vtpm"
|
||||
SNPvTPM = "snp-vtpm"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
cfgString string
|
||||
timeout time.Duration
|
||||
maxRetryDelay time.Duration
|
||||
platformInfo string
|
||||
stepping string
|
||||
trustedAuthorKeys []string
|
||||
trustedAuthorHashes []string
|
||||
trustedIdKeys []string
|
||||
trustedIdKeyHashes []string
|
||||
attestationFile string
|
||||
attestation []byte
|
||||
empty16 = [size16]byte{}
|
||||
empty32 = [size32]byte{}
|
||||
empty64 = [size64]byte{}
|
||||
defaultReportIdMa = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||
getJsonAttestation bool
|
||||
errReportSize = errors.New("attestation contents too small")
|
||||
mode string
|
||||
cfg = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
cfgString string
|
||||
timeout time.Duration
|
||||
maxRetryDelay time.Duration
|
||||
platformInfo string
|
||||
stepping string
|
||||
trustedAuthorKeys []string
|
||||
trustedAuthorHashes []string
|
||||
trustedIdKeys []string
|
||||
trustedIdKeyHashes []string
|
||||
attestationFile string
|
||||
tpmAttestationFile string
|
||||
attestation []byte
|
||||
empty16 = [size16]byte{}
|
||||
empty32 = [size32]byte{}
|
||||
empty64 = [size64]byte{}
|
||||
defaultReportIdMa = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||
errReportSize = errors.New("attestation contents too small")
|
||||
ErrBadAttestation = errors.New("attestation file is corrupted or in wrong format")
|
||||
output string
|
||||
nonce []byte
|
||||
format string
|
||||
teeNonce []byte
|
||||
getTextProtoAttestation bool
|
||||
)
|
||||
|
||||
var errEmptyFile = errors.New("input file is empty")
|
||||
|
||||
var marshalOptions = prototext.MarshalOptions{
|
||||
Multiline: true,
|
||||
EmitASCII: true,
|
||||
}
|
||||
var unmarshalOptions = prototext.UnmarshalOptions{}
|
||||
|
||||
func (cli *CLI) NewAttestationCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "attestation [command]",
|
||||
@@ -153,31 +182,75 @@ func (cli *CLI) NewAttestationCmd() *cobra.Command {
|
||||
|
||||
func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Retrieve attestation information from agent. Report data expected in hex enoded string of length 64 bytes.",
|
||||
Example: "get <report_data>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Use: "get",
|
||||
Short: "Retrieve attestation information from agent. The argument of the command must be the type of the report (snp or vtpm or snp-vtpm).",
|
||||
ValidArgs: []cobra.Completion{SNP, VTPM, SNPvTPM},
|
||||
Example: fmt.Sprintf(`Based on attestation report type:
|
||||
get %s --tee <512 bit hex value>
|
||||
get %s --vtpm <256 bit hex value>
|
||||
get %s --tee <512 bit hex value> --vtpm <256 bit hex value>`, SNP, VTPM, SNPvTPM),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Getting attestation")
|
||||
|
||||
reportData, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding report data: %v ❌ ", err)
|
||||
if err := cobra.OnlyValidArgs(cmd, args); err != nil {
|
||||
printError(cmd, "Bad attestation type: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
if len(reportData) != agent.ReportDataSize {
|
||||
msg := color.New(color.FgRed).Sprintf("report data must be a hex encoded string of length %d bytes ❌ ", agent.ReportDataSize)
|
||||
|
||||
attestationType := args[0]
|
||||
|
||||
attType := config.SNP
|
||||
switch attestationType {
|
||||
case SNP:
|
||||
cmd.Println("Fetching SEV-SNP attestation report")
|
||||
case VTPM:
|
||||
cmd.Println("Fetching vTPM report")
|
||||
attType = config.VTPM
|
||||
case SNPvTPM:
|
||||
cmd.Println("Fetching SEV-SNP and vTPM report")
|
||||
attType = config.SNPvTPM
|
||||
}
|
||||
|
||||
if (attType == config.VTPM || attType == config.SNPvTPM) && len(nonce) == 0 {
|
||||
msg := color.New(color.FgRed).Sprint("vTPM nonce must be defined for vTPM attestation ❌ ")
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if (attType == config.SNP || attType == config.SNPvTPM) && len(teeNonce) == 0 {
|
||||
msg := color.New(color.FgRed).Sprint("TEE nonce must be defined for SEV-SNP attestation ❌ ")
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
var fixedReportData [quoteprovider.Nonce]byte
|
||||
if attType != config.VTPM {
|
||||
if len(teeNonce) > quoteprovider.Nonce {
|
||||
msg := color.New(color.FgRed).Sprintf("nonce must be a hex encoded string of length lesser or equal %d bytes ❌ ", quoteprovider.Nonce)
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
copy(fixedReportData[:], teeNonce)
|
||||
}
|
||||
|
||||
var fixedVtpmNonceByte [vtpm.Nonce]byte
|
||||
if attType != config.SNP {
|
||||
if len(nonce) > vtpm.Nonce {
|
||||
msg := color.New(color.FgRed).Sprintf("vTPM nonce must be a hex encoded string of length lesser or equal %d bytes ❌ ", vtpm.Nonce)
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
|
||||
copy(fixedVtpmNonceByte[:], nonce)
|
||||
}
|
||||
|
||||
filename := attestationFilePath
|
||||
if getJsonAttestation {
|
||||
if getTextProtoAttestation {
|
||||
filename = attestationJson
|
||||
}
|
||||
|
||||
@@ -187,7 +260,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
if err := cli.agentSDK.Attestation(cmd.Context(), [agent.ReportDataSize]byte(reportData), attestationFile); err != nil {
|
||||
if err := cli.agentSDK.Attestation(cmd.Context(), fixedReportData, fixedVtpmNonceByte, int(attType), attestationFile); err != nil {
|
||||
printError(cmd, "Failed to get attestation due to error: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
@@ -197,16 +270,32 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
if getJsonAttestation {
|
||||
if getTextProtoAttestation {
|
||||
result, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err = attesationToJSON(result)
|
||||
switch attestationType {
|
||||
case SNP:
|
||||
result, err = attesationToJSON(result)
|
||||
case VTPM, SNPvTPM:
|
||||
marshalOptions := prototext.MarshalOptions{
|
||||
Multiline: true,
|
||||
EmitASCII: true,
|
||||
}
|
||||
var attvTPM tpmAttest.Attestation
|
||||
err = proto.Unmarshal(result, &attvTPM)
|
||||
if err != nil {
|
||||
printError(cmd, "failed to unmarshal the attestation report: %v ❌ ", ErrBadAttestation)
|
||||
}
|
||||
|
||||
result = []byte(marshalOptions.Format(&attvTPM))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting attestation to json: %v ❌ ", err)
|
||||
printError(cmd, "Error converting attestation to textproto: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -220,7 +309,9 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&getJsonAttestation, "json", "j", false, "Get attestation in json format")
|
||||
cmd.Flags().BoolVarP(&getTextProtoAttestation, "textproto", "p", false, "Get attestation in textproto format")
|
||||
cmd.Flags().BytesHexVarP(&teeNonce, "tee", "e", []byte{}, "Define the nonce for the SNP attestation report (must be used with attestation type snp and snp-vtpm)")
|
||||
cmd.Flags().BytesHexVarP(&nonce, "vtpm", "t", []byte{}, "Define the nonce for the vTPM attestation report (must be used with attestation type vtpm and snp-vtpm)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -252,52 +343,106 @@ func isFileJSON(filename string) bool {
|
||||
|
||||
func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "validate",
|
||||
Short: "Validate and verify attestation information. The report is provided as a file path.",
|
||||
Example: "validate <attestation_report_file_path>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Println("Checking attestation")
|
||||
Use: "validate",
|
||||
Short: "Validate and verify attestation information. You can choose from 3 modes: snp,vtpm and snp-vtpm.Default mode is snp.",
|
||||
Example: `Based on mode:
|
||||
validate <attestationreportfilepath> --report_data <reportdata> --product <product data> //default
|
||||
validate --mode snp <attestationreportfilepath> --report_data <reportdata> --product <product data>
|
||||
validate --mode vtpm <attestationreportfilepath> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>
|
||||
validate --mode snp-vtpm <attestationreportfilepath> --report_data <reportdata> --product <product data> --nonce <noncevalue> --format <formatvalue> --output <outputvalue>`,
|
||||
|
||||
attestationFile = string(args[0])
|
||||
|
||||
if err := parseConfig(); err != nil {
|
||||
printError(cmd, "Error parsing config: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
if err := parseHashes(); err != nil {
|
||||
printError(cmd, "Error parsing hashes: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
if err := parseFiles(); err != nil {
|
||||
printError(cmd, "Error parsing files: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
// This format is the attestation report in AMD's specified ABI format, immediately
|
||||
// followed by the certificate table bytes.
|
||||
if len(attestation) < abi.ReportSize {
|
||||
msg := color.New(color.FgRed).Sprintf("attestation contents too small (0x%x bytes). Want at least 0x%x bytes ❌ ", len(attestation), abi.ReportSize)
|
||||
cmd.Println(msg)
|
||||
return
|
||||
}
|
||||
if err := parseUints(); err != nil {
|
||||
printError(cmd, "Error parsing uints: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
cfg.Policy.Vmpl = wrapperspb.UInt32(0)
|
||||
|
||||
if err := validateInput(); err != nil {
|
||||
printError(cmd, "Error validating input: %v ❌ ", err)
|
||||
return
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
mode, _ := cmd.Flags().GetString("mode")
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("please pass the attestation report file path")
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(attestation, &cfg); err != nil {
|
||||
printError(cmd, "Attestation validation and verification failed with error: %v ❌ ", err)
|
||||
return
|
||||
// Validate flags based on the mode
|
||||
switch mode {
|
||||
case "snp":
|
||||
if err := cmd.MarkFlagRequired("report_data"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'report_data' as required for SEV-SNP mode: %v", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("product"); err != nil {
|
||||
return fmt.Errorf("failed to mark flag as required: %v ❌ ", err)
|
||||
}
|
||||
case "snp-vtpm":
|
||||
if err := cmd.MarkFlagRequired("nonce"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'nonce' as required for vTPM mode: %v", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("report_data"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'report_data' as required for SEV-SNP mode: %v", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("product"); err != nil {
|
||||
return fmt.Errorf("failed to mark flag as required: %v ❌ ", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("format"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'format' as required for vTPM mode: %v", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("output"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'output' as required for vTPM mode: %v", err)
|
||||
}
|
||||
case "vtpm":
|
||||
if err := cmd.MarkFlagRequired("nonce"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'nonce' as required for vTPM mode: %v", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("format"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'format' as required for vTPM mode: %v", err)
|
||||
}
|
||||
if err := cmd.MarkFlagRequired("output"); err != nil {
|
||||
return fmt.Errorf("failed to mark 'output' as required for vTPM mode: %v", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown mode: %s", mode)
|
||||
}
|
||||
cmd.Println("Attestation validation and verification is successful!")
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
mode, _ := cmd.Flags().GetString("mode")
|
||||
switch mode {
|
||||
case "snp":
|
||||
return sevsnpverify(cmd, args)
|
||||
case "snp-vtpm":
|
||||
return vtpmSevSnpverify(args)
|
||||
case "vtpm":
|
||||
return vtpmverify(args)
|
||||
default:
|
||||
return fmt.Errorf("unknown mode: %s", mode)
|
||||
}
|
||||
},
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
cmd.Flags().StringVar(
|
||||
&mode,
|
||||
"mode",
|
||||
"snp", // default mode
|
||||
"The attestation validation mode. Example: sevsnp",
|
||||
)
|
||||
|
||||
// VTPM FLAGS
|
||||
cmd.Flags().BytesHexVar(
|
||||
&nonce,
|
||||
"nonce",
|
||||
[]byte{},
|
||||
"hex encoded nonce for vTPM attestation, cannot be empty",
|
||||
)
|
||||
|
||||
cmd.Flags().StringVar(
|
||||
&format,
|
||||
"format",
|
||||
"binarypb", // default value
|
||||
"type of output file where attestation report stored <binarypb|textproto>",
|
||||
)
|
||||
|
||||
cmd.Flags().StringVar(
|
||||
&output,
|
||||
"output",
|
||||
"",
|
||||
"output file",
|
||||
)
|
||||
|
||||
// SEV-SNP FLAGS
|
||||
cmd.Flags().StringVar(
|
||||
&cfgString,
|
||||
"config",
|
||||
@@ -473,19 +618,221 @@ func (cli *CLI) NewValidateAttestationValidationCmd() *cobra.Command {
|
||||
"PEM format CA bundles for the AMD product. Combined with contents of cabundle_paths.",
|
||||
)
|
||||
|
||||
if err := cmd.MarkFlagRequired("report_data"); err != nil {
|
||||
printError(cmd, "Failed to mark flag as required: %v ❌ ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cmd.MarkFlagRequired("product"); err != nil {
|
||||
printError(cmd, "Failed to mark flag as required: %v ❌ ", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (cli *CLI) NewMeasureCmd(igvmBinaryPath string) *cobra.Command {
|
||||
igvmmeasureCmd := &cobra.Command{
|
||||
Use: "igvmmeasure <INPUT>",
|
||||
Short: "Measure an IGVM file",
|
||||
Long: `igvmmeasure measures an IGVM file and outputs the calculated measurement.
|
||||
It ensures integrity verification for the IGVM file.`,
|
||||
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("error: No input file provided")
|
||||
}
|
||||
|
||||
inputFile := args[0]
|
||||
|
||||
measurement, err := cli.measurement.Run(inputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputString := string(measurement)
|
||||
lines := strings.Split(strings.TrimSpace(outputString), "\n")
|
||||
|
||||
if len(lines) == 1 {
|
||||
outputString = strings.ToLower(outputString)
|
||||
} else {
|
||||
return fmt.Errorf("error: %s", outputString)
|
||||
}
|
||||
|
||||
cmd.Print(outputString)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return igvmmeasureCmd
|
||||
}
|
||||
|
||||
func sevsnpverify(cmd *cobra.Command, args []string) error {
|
||||
cmd.Println("Checking attestation")
|
||||
|
||||
attestationFile = string(args[0])
|
||||
if err := parseAttestationFile(); err != nil {
|
||||
return fmt.Errorf("error parsing config: %v ❌ ", err)
|
||||
}
|
||||
|
||||
// This format is the attestation report in AMD's specified ABI format, immediately
|
||||
// followed by the certificate table bytes.
|
||||
if len(attestation) < abi.ReportSize {
|
||||
return fmt.Errorf("attestation too small: got 0x%x bytes, need at least 0x%x bytes", len(attestation), abi.ReportSize)
|
||||
}
|
||||
|
||||
if err := parseAttestationConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attestationPB, err := abi.ReportCertsToProto(attestation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert attestation bytes to struct %v ❌ ", err)
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(attestationPB, &cfg); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
|
||||
cmd.Println("Attestation validation and verification is successful!")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAttestationConfig() error {
|
||||
if err := parseConfig(); err != nil {
|
||||
return fmt.Errorf("error parsing config: %v ❌ ", err)
|
||||
}
|
||||
if err := parseHashes(); err != nil {
|
||||
return fmt.Errorf("error parsing hashes: %v ❌ ", err)
|
||||
}
|
||||
if err := parseTrustedKeys(); err != nil {
|
||||
return fmt.Errorf("error parsing files: %v ❌ ", err)
|
||||
}
|
||||
|
||||
if err := parseUints(); err != nil {
|
||||
return fmt.Errorf("error parsing uints: %v ❌ ", err)
|
||||
}
|
||||
|
||||
if err := validateInput(); err != nil {
|
||||
return fmt.Errorf("error validating input: %v ❌ ", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vtpmSevSnpverify(args []string) error {
|
||||
attestation, err := returnvTPMAttestation(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var teeAttestation *sevsnp.Attestation
|
||||
switch attestation.GetTeeAttestation().(type) {
|
||||
case *tpmAttest.Attestation_SevSnpAttestation:
|
||||
teeAttestation = attestation.GetSevSnpAttestation()
|
||||
default:
|
||||
teeAttestation = nil
|
||||
}
|
||||
|
||||
if teeAttestation == nil {
|
||||
return fmt.Errorf("tee attestation not fetched")
|
||||
}
|
||||
|
||||
if err := parseAttestationConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAndValidate(teeAttestation, &cfg); err != nil {
|
||||
return fmt.Errorf("attestation validation and verification failed with error: %v ❌ ", err)
|
||||
}
|
||||
|
||||
if err := verifyvTPM(attestation); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vtpmverify(args []string) error {
|
||||
attestation, err := returnvTPMAttestation(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := verifyvTPM(attestation); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyvTPM(attestation *tpmAttest.Attestation) error {
|
||||
pub, err := tpm2.DecodePublic(attestation.GetAkPub())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cryptoPub, err := pub.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ms, err := server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: nonce, TrustedAKs: []crypto.PublicKey{cryptoPub}, TEEOpts: nil})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out, err := marshalOptions.Marshal(ms)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
output, err := createOutputFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if closer, ok := output.(*os.File); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
if _, err := output.Write(out); err != nil {
|
||||
return fmt.Errorf("failed to write verified attestation report: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func returnvTPMAttestation(args []string) (*tpmAttest.Attestation, error) {
|
||||
tpmAttestationFile = string(args[0])
|
||||
input, err := openInputFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if closer, ok := input.(*os.File); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
attestationBytes, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attestation := &tpmAttest.Attestation{}
|
||||
|
||||
if format == FormatBinaryPB {
|
||||
err = proto.Unmarshal(attestationBytes, attestation)
|
||||
} else if format == FormatTextProto {
|
||||
err = unmarshalOptions.Unmarshal(attestationBytes, attestation)
|
||||
} else {
|
||||
return nil, fmt.Errorf("format should be either binarypb or textproto")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to unmarshal attestation report: %v", err)
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func openInputFile() (io.Reader, error) {
|
||||
if tpmAttestationFile == "" {
|
||||
return nil, errEmptyFile
|
||||
}
|
||||
return os.Open(tpmAttestationFile)
|
||||
}
|
||||
|
||||
func createOutputFile() (io.Writer, error) {
|
||||
if output == "" {
|
||||
return os.Stdout, nil
|
||||
}
|
||||
return os.Create(output)
|
||||
}
|
||||
|
||||
// parseConfig decodes config passed as json for check.Config struct.
|
||||
// example
|
||||
/* {
|
||||
@@ -574,7 +921,7 @@ func parseHashes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseFiles() error {
|
||||
func parseAttestationFile() error {
|
||||
file, err := os.ReadFile(attestationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -587,6 +934,10 @@ func parseFiles() error {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTrustedKeys() error {
|
||||
for _, path := range trustedAuthorKeys {
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
||||
+138
-7
@@ -3,15 +3,22 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/gcp"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
@@ -99,6 +106,126 @@ func (cli *CLI) NewAddHostDataCmd() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *CLI) NewGCPAttestationPolicy() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "gcp",
|
||||
Short: "Get attestation policy for GCP CVM",
|
||||
Example: `gcp <bin_vtmp_attestation_report_file> <vcpu_count>`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
attestationBin, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
vcpuCount, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting vCPU count to integer: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestation := &attest.Attestation{}
|
||||
|
||||
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
|
||||
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestationPB := attestation.GetSevSnpAttestation()
|
||||
|
||||
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
|
||||
if err != nil {
|
||||
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
launchEndorsement, err := gcp.GetLaunchEndorsement(cmd.Context(), measurement)
|
||||
if err != nil {
|
||||
printError(cmd, "Error getting launch endorsement: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestationPolicy, err := gcp.GenerateAttestationPolicy(launchEndorsement, uint32(vcpuCount))
|
||||
if err != nil {
|
||||
printError(cmd, "Error generating attestation policy: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestationPolicyJson, err := json.MarshalIndent(attestationPolicy, "", " ")
|
||||
if err != nil {
|
||||
printError(cmd, "Error marshaling attestation policy: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.WriteFile("attestation_policy.json", attestationPolicyJson, filePermission); err != nil {
|
||||
printError(cmd, "Error writing attestation policy file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Attestation policy file generated successfully ✅")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "download",
|
||||
Short: "Download GCP OVMF file",
|
||||
Example: `download <bin_vtmp_attestation_report_file>`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
attestationBin, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation report file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestation := &attest.Attestation{}
|
||||
|
||||
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
|
||||
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
attestationPB := attestation.GetSevSnpAttestation()
|
||||
|
||||
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
|
||||
if err != nil {
|
||||
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
launchEndorsement, err := gcp.GetLaunchEndorsement(cmd.Context(), measurement)
|
||||
if err != nil {
|
||||
printError(cmd, "Error getting launch endorsement: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
ovmf, err := gcp.DownloadOvmfFile(cmd.Context(), fmt.Sprintf("%x", launchEndorsement.Digest))
|
||||
if err != nil {
|
||||
printError(cmd, "Error downloading OVMF file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
sum384 := sha512.Sum384(ovmf)
|
||||
|
||||
if !bytes.Equal(sum384[:], launchEndorsement.Digest) {
|
||||
printError(cmd, "Error OVMF file does not match the measurement: %v ❌ ", fmt.Errorf("digest mismatch"))
|
||||
} else {
|
||||
cmd.Println("OVMF firmware in vm is unmodified ✅")
|
||||
}
|
||||
|
||||
if err := os.WriteFile("ovmf.fd", ovmf, filePermission); err != nil {
|
||||
printError(cmd, "Error writing OVMF file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("OVMF file downloaded successfully ✅")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func changeAttestationConfiguration(fileName, base64Data string, expectedLength int, field fieldType) error {
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
@@ -109,27 +236,31 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
|
||||
return errDataLength
|
||||
}
|
||||
|
||||
ac := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
ac := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
attestationPolicy, err := os.ReadFile(fileName)
|
||||
f, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return errors.Wrap(errReadingAttestationPolicyFile, err)
|
||||
}
|
||||
|
||||
if err = protojson.Unmarshal(attestationPolicy, &ac); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(f, &ac); err != nil {
|
||||
return errors.Wrap(errUnmarshalJSON, err)
|
||||
}
|
||||
|
||||
if ac.Config.Policy == nil {
|
||||
ac.Config.Policy = &check.Policy{}
|
||||
}
|
||||
|
||||
switch field {
|
||||
case measurementField:
|
||||
ac.Policy.Measurement = data
|
||||
ac.Config.Policy.Measurement = data
|
||||
case hostDataField:
|
||||
ac.Policy.HostData = data
|
||||
ac.Config.Policy.HostData = data
|
||||
default:
|
||||
return errAttestationPolicyField
|
||||
}
|
||||
|
||||
fileJson, err := protojson.Marshal(&ac)
|
||||
fileJson, err := json.MarshalIndent(&ac, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(errMarshalJSON, err)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
@@ -18,9 +19,9 @@ func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
initialConfig := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
initialConfig := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
initialJSON, err := protojson.Marshal(&initialConfig)
|
||||
initialJSON, err := json.Marshal(initialConfig)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(tmpfile.Name(), initialJSON, 0o644)
|
||||
require.NoError(t, err)
|
||||
@@ -86,15 +87,15 @@ func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
content, err := os.ReadFile(tmpfile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
config := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
err = protojson.Unmarshal(content, &config)
|
||||
ap := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err = config.ReadAttestationPolicyFromByte(content, &ap)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedData, _ := base64.StdEncoding.DecodeString(tt.base64Data)
|
||||
if tt.field == measurementField {
|
||||
assert.Equal(t, decodedData, config.Policy.Measurement)
|
||||
assert.Equal(t, decodedData, ap.Config.Policy.Measurement)
|
||||
} else if tt.field == hostDataField {
|
||||
assert.Equal(t, decodedData, config.Policy.HostData)
|
||||
assert.Equal(t, decodedData, ap.Config.Policy.HostData)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+208
-30
@@ -14,10 +14,12 @@ import (
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"github.com/ultravioletrs/cocos/pkg/sdk/mocks"
|
||||
)
|
||||
|
||||
@@ -32,10 +34,8 @@ func TestNewAttestationCmd(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
cmd.SetOut(&buf)
|
||||
|
||||
cmd.SetOutput(&buf)
|
||||
|
||||
reportData := bytes.Repeat([]byte{0x01}, agent.ReportDataSize)
|
||||
mockSDK.On("Attestation", mock.Anything, [agent.ReportDataSize]byte(reportData), mock.Anything).Return(nil)
|
||||
reportData := bytes.Repeat([]byte{0x01}, quoteprovider.Nonce)
|
||||
mockSDK.On("Attestation", mock.Anything, [quoteprovider.Nonce]byte(reportData), mock.Anything).Return(nil)
|
||||
|
||||
cmd.SetArgs([]string{hex.EncodeToString(reportData)})
|
||||
err := cmd.Execute()
|
||||
@@ -46,6 +46,10 @@ func TestNewAttestationCmd(t *testing.T) {
|
||||
func TestNewGetAttestationCmd(t *testing.T) {
|
||||
validattestation, err := os.ReadFile("../attestation.bin")
|
||||
require.NoError(t, err)
|
||||
|
||||
teeNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce))
|
||||
vtpmNonce := hex.EncodeToString(bytes.Repeat([]byte{0x00}, vtpm.Nonce))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -55,57 +59,85 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
expectedOut string
|
||||
}{
|
||||
{
|
||||
name: "successful attestation retrieval",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
|
||||
name: "successful SNP attestation retrieval",
|
||||
args: []string{"snp", "--tee", teeNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "invalid report data (decoding error)",
|
||||
args: []string{"invalid"},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Error decoding report data",
|
||||
name: "successful vTPM attestation retrieval",
|
||||
args: []string{"vtpm", "--vtpm", vtpmNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "successful SNP-vTPM attestation retrieval",
|
||||
args: []string{"snp-vtpm", "--tee", teeNonce, "--vtpm", vtpmNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "missing vTPM nonce",
|
||||
args: []string{"snp-vtpm", "--tee", teeNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "vTPM nonce must be defined for vTPM attestation",
|
||||
},
|
||||
{
|
||||
name: "missing TEE nonce",
|
||||
args: []string{"snp-vtpm", "--vtpm", vtpmNonce},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedOut: "TEE nonce must be defined for SEV-SNP attestation",
|
||||
},
|
||||
{
|
||||
name: "invalid report data size",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, 32))},
|
||||
args: []string{"snp", "--tee", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 65))},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "report data must be a hex encoded string of length 64 bytes",
|
||||
expectedErr: "nonce must be a hex encoded string of length lesser or equal 64 bytes",
|
||||
},
|
||||
{
|
||||
name: "invalid report data hex",
|
||||
name: "invalid vTPM data size",
|
||||
args: []string{"vtpm", "-t", hex.EncodeToString(bytes.Repeat([]byte{0x00}, 33))},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "vTPM nonce must be a hex encoded string of length lesser or equal 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "invalid arguments",
|
||||
args: []string{"invalid"},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Error decoding report data",
|
||||
expectedErr: "Bad attestation type: invalid argument ",
|
||||
},
|
||||
{
|
||||
name: "failed to get attestation",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
|
||||
args: []string{"snp", "-e", teeNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("error"),
|
||||
expectedErr: "Failed to get attestation due to error",
|
||||
},
|
||||
{
|
||||
name: "JSON report error",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), "--json"},
|
||||
name: "Textproto report error",
|
||||
args: []string{"snp", "-e", teeNonce, "--textproto"},
|
||||
mockResponse: []byte("mock attestation"),
|
||||
mockError: nil,
|
||||
expectedErr: "Error converting attestation to json",
|
||||
expectedErr: "Error converting attestation to textproto",
|
||||
},
|
||||
{
|
||||
name: "successful JSON report",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), "--json"},
|
||||
name: "successful Textproto report",
|
||||
args: []string{"snp", "-e", teeNonce, "--textproto"},
|
||||
mockResponse: validattestation,
|
||||
mockError: nil,
|
||||
expectedOut: "Attestation result retrieved and saved successfully!",
|
||||
},
|
||||
{
|
||||
name: "connection error",
|
||||
args: []string{hex.EncodeToString(bytes.Repeat([]byte{0x01}, agent.ReportDataSize))},
|
||||
args: []string{"snp", "-e", teeNonce},
|
||||
mockResponse: nil,
|
||||
mockError: errors.New("failed to connect to agent"),
|
||||
expectedErr: "Failed to connect to agent",
|
||||
@@ -125,10 +157,10 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
}
|
||||
cmd := cli.NewGetAttestationCmd()
|
||||
var buf bytes.Buffer
|
||||
cmd.SetOutput(&buf)
|
||||
cmd.SetOut(&buf)
|
||||
|
||||
mockSDK.On("Attestation", mock.Anything, [agent.ReportDataSize]byte(bytes.Repeat([]byte{0x01}, agent.ReportDataSize)), mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
|
||||
_, err := args.Get(2).(*os.File).Write(tc.mockResponse)
|
||||
mockSDK.On("Attestation", mock.Anything, [quoteprovider.Nonce]byte(bytes.Repeat([]byte{0x00}, quoteprovider.Nonce)), [vtpm.Nonce]byte(bytes.Repeat([]byte{0x00}, vtpm.Nonce)), mock.Anything, mock.Anything).Return(tc.mockError).Run(func(args mock.Arguments) {
|
||||
_, err := args.Get(4).(*os.File).Write(tc.mockResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@@ -145,12 +177,12 @@ func TestNewGetAttestationCmd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewValidateAttestationValidationCmd(t *testing.T) {
|
||||
func TestNewValidateAttestationValidationCmdDefaults(t *testing.T) {
|
||||
cli := &CLI{}
|
||||
cmd := cli.NewValidateAttestationValidationCmd()
|
||||
|
||||
assert.Equal(t, "validate", cmd.Use)
|
||||
assert.Equal(t, "Validate and verify attestation information. The report is provided as a file path.", cmd.Short)
|
||||
assert.Equal(t, "Validate and verify attestation information. You can choose from 3 modes: snp,vtpm and snp-vtpm.Default mode is snp.", cmd.Short)
|
||||
|
||||
assert.Equal(t, fmt.Sprint(defaultMinimumTcb), cmd.Flag("minimum_tcb").Value.String())
|
||||
assert.Equal(t, fmt.Sprint(defaultMinimumLaunchTcb), cmd.Flag("minimum_lauch_tcb").Value.String())
|
||||
@@ -162,6 +194,150 @@ func TestNewValidateAttestationValidationCmd(t *testing.T) {
|
||||
assert.Equal(t, fmt.Sprint(defaultMaxRetryDelay), cmd.Flag("max_retry_delay").Value.String())
|
||||
}
|
||||
|
||||
func TestNewValidateAttestationValidationCmd(t *testing.T) {
|
||||
cli := &CLI{}
|
||||
cmd := cli.NewValidateAttestationValidationCmd()
|
||||
|
||||
t.Run("missing attestation report file path", func(t *testing.T) {
|
||||
err := cmd.Execute()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "please pass the attestation report file path", err.Error())
|
||||
})
|
||||
|
||||
t.Run("unknown mode", func(t *testing.T) {
|
||||
cmd.SetArgs([]string{attestationFilePath, "--mode=invalid"})
|
||||
err := cmd.Execute()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown mode")
|
||||
})
|
||||
|
||||
t.Run("snp mode with missing flags", func(t *testing.T) {
|
||||
cmd.SetArgs([]string{attestationFilePath, "--mode=snp"})
|
||||
err := cmd.Execute()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "required flag(s) \"product\", \"report_data\" not set")
|
||||
})
|
||||
|
||||
t.Run("vtpm mode with missing flags", func(t *testing.T) {
|
||||
cmd.SetArgs([]string{vtpmFilePath, "--mode=vtpm"})
|
||||
err := cmd.Execute()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "required flag(s) \"format\", \"nonce\", \"output\", \"product\", \"report_data\" not set")
|
||||
})
|
||||
|
||||
t.Run("snp-vtpm mode with missing flags", func(t *testing.T) {
|
||||
cmd.SetArgs([]string{vtpmFilePath, "--mode=snp-vtpm"})
|
||||
err := cmd.Execute()
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "required flag(s) \"format\", \"nonce\", \"output\", \"product\", \"report_data\" not set")
|
||||
})
|
||||
|
||||
t.Run("valid snp mode execution", func(t *testing.T) {
|
||||
cli := CLI{}
|
||||
cmd := cli.NewValidateAttestationValidationCmd()
|
||||
|
||||
cmd.RunE = func(_ *cobra.Command, _ []string) error {
|
||||
t.Log("Mock RunE executed instead of sevsnpverify")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd.SetArgs([]string{
|
||||
"../attestation.bin",
|
||||
"--mode=snp",
|
||||
"--report_data=" +
|
||||
"11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff" +
|
||||
"11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff",
|
||||
"--product=Milan",
|
||||
})
|
||||
err := cmd.PreRunE(cmd, []string{"../attestation.bin"})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("valid vtpm mode execution", func(t *testing.T) {
|
||||
cli := CLI{}
|
||||
cmd := cli.NewValidateAttestationValidationCmd()
|
||||
|
||||
cmd.RunE = func(_ *cobra.Command, _ []string) error {
|
||||
t.Log("Mock RunE executed instead of vtpmverify")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd.SetArgs([]string{vtpmFilePath, "--mode=vtpm", "--nonce=123abc", "--format=binarypb", "--output=some_output"})
|
||||
|
||||
err := cmd.PreRunE(cmd, []string{"../quote.dat"})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("valid snp-vtpm mode execution", func(t *testing.T) {
|
||||
cli := CLI{}
|
||||
cmd := cli.NewValidateAttestationValidationCmd()
|
||||
|
||||
cmd.RunE = func(_ *cobra.Command, _ []string) error {
|
||||
t.Log("Mock RunE executed instead of vtpmSevSnpverify")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd.SetArgs([]string{vtpmFilePath, "--mode=snp-vtpm", "--nonce=123abc", "--format=textproto", "--output=some_output"})
|
||||
err := cmd.PreRunE(cmd, []string{"../quote.dat"})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
type MockMeasurement struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockMeasurement) Run(igvmBinaryPath string) ([]byte, error) {
|
||||
args := m.Called(igvmBinaryPath)
|
||||
return nil, args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockMeasurement) Stop() error {
|
||||
args := m.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestNewMeasureCmd_RunSuccess(t *testing.T) {
|
||||
cliInstance := &CLI{}
|
||||
mockMeasurement := new(MockMeasurement)
|
||||
cliInstance.measurement = mockMeasurement
|
||||
|
||||
mockMeasurement.On("Run", "testfile.igvm").Return(nil)
|
||||
|
||||
cmd := cliInstance.NewMeasureCmd("fake_binary_path")
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetErr(buf)
|
||||
cmd.SetArgs([]string{"testfile.igvm"})
|
||||
|
||||
err := cmd.Execute()
|
||||
|
||||
assert.NoError(t, err)
|
||||
mockMeasurement.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestNewMeasureCmd_RunError(t *testing.T) {
|
||||
cliInstance := &CLI{}
|
||||
mockMeasurement := new(MockMeasurement)
|
||||
cliInstance.measurement = mockMeasurement
|
||||
expectedError := errors.New("mocked measurement error")
|
||||
|
||||
mockMeasurement.On("Run", "testfile.igvm").Return(expectedError)
|
||||
|
||||
cmd := cliInstance.NewMeasureCmd("fake_binary_path")
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
cmd.SetErr(buf)
|
||||
cmd.SetArgs([]string{"testfile.igvm"})
|
||||
|
||||
err := cmd.Execute()
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedError.Error(), err.Error())
|
||||
mockMeasurement.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestParseConfig(t *testing.T) {
|
||||
cfgString = ""
|
||||
err := parseConfig()
|
||||
@@ -214,7 +390,9 @@ func TestParseFiles(t *testing.T) {
|
||||
trustedAuthorKeys = []string{authorKeyFile}
|
||||
trustedIdKeys = []string{idKeyFile}
|
||||
|
||||
err = parseFiles()
|
||||
err = parseAttestationFile()
|
||||
assert.NoError(t, err)
|
||||
err = parseTrustedKeys()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("test attestation"), attestation)
|
||||
assert.Len(t, cfg.Policy.TrustedAuthorKeys, 1)
|
||||
@@ -225,7 +403,7 @@ func TestParseFiles(t *testing.T) {
|
||||
os.Remove(idKeyFile)
|
||||
|
||||
attestationFile = "non_existent_file.bin"
|
||||
err = parseFiles()
|
||||
err = parseAttestationFile()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,14 +27,14 @@ func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
|
||||
Example: "ca-bundle <path_to_platform_info_json>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
attestationConfiguration := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
err := grpc.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
attestationConfiguration := config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err := config.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
if err != nil {
|
||||
printError(cmd, "Error while reading manifest: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
product := attestationConfiguration.RootOfTrust.ProductLine
|
||||
product := attestationConfiguration.Config.RootOfTrust.ProductLine
|
||||
|
||||
getter := trust.DefaultHTTPSGetter()
|
||||
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
|
||||
|
||||
+19
-3
@@ -4,6 +4,7 @@ package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -15,7 +16,9 @@ const (
|
||||
serverCA = "server-ca"
|
||||
clientKey = "client-key"
|
||||
clientCrt = "client-crt"
|
||||
caUrl = "ca-url"
|
||||
logLevel = "log-level"
|
||||
ttlFlag = "ttl"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -23,7 +26,9 @@ var (
|
||||
agentCVMServerCA string
|
||||
agentCVMClientKey string
|
||||
agentCVMClientCrt string
|
||||
agentCVMCaUrl string
|
||||
agentLogLevel string
|
||||
ttl time.Duration
|
||||
)
|
||||
|
||||
func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
@@ -47,6 +52,11 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
|
||||
createReq.AgentCvmServerUrl = agentCVMServerUrl
|
||||
createReq.AgentLogLevel = agentLogLevel
|
||||
createReq.AgentCvmCaUrl = agentCVMCaUrl
|
||||
|
||||
if ttl > 0 {
|
||||
createReq.Ttl = ttl.String()
|
||||
}
|
||||
|
||||
cmd.Println("🔗 Creating a new virtual machine")
|
||||
|
||||
@@ -56,7 +66,7 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println(color.New(color.FgGreen).Sprintf("✅ Virtual machine created successfully with id %s and port %s", res.SvmId, res.ForwardedPort))
|
||||
cmd.Println(color.New(color.FgGreen).Sprintf("✅ Virtual machine created successfully with id %s and port %s", res.CvmId, res.ForwardedPort))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -64,7 +74,13 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
cmd.Flags().StringVar(&agentCVMServerCA, serverCA, "", "CVM server CA")
|
||||
cmd.Flags().StringVar(&agentCVMClientKey, clientKey, "", "CVM client key")
|
||||
cmd.Flags().StringVar(&agentCVMClientCrt, clientCrt, "", "CVM client crt")
|
||||
cmd.Flags().StringVar(&agentCVMCaUrl, agentCVMCaUrl, "", "CVM CA service URL")
|
||||
cmd.Flags().StringVar(&agentLogLevel, logLevel, "", "Agent Log level")
|
||||
cmd.Flags().DurationVar(&ttl, ttlFlag, 0, "TTL for the VM")
|
||||
if err := cmd.MarkFlagRequired(serverURL); err != nil {
|
||||
printError(cmd, "Error marking flag as required: %v ❌ ", err)
|
||||
return cmd
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -73,7 +89,7 @@ func (c *CLI) NewRemoveVMCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "remove-vm",
|
||||
Short: "Remove a virtual machine",
|
||||
Example: `remove-vm <svm_id>`,
|
||||
Example: `remove-vm <cvm_id>`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := c.InitializeManagerClient(cmd); err == nil {
|
||||
@@ -87,7 +103,7 @@ func (c *CLI) NewRemoveVMCmd() *cobra.Command {
|
||||
|
||||
cmd.Println("🔗 Removing virtual machine")
|
||||
|
||||
_, err := c.managerClient.RemoveVm(cmd.Context(), &manager.RemoveReq{SvmId: args[0]})
|
||||
_, err := c.managerClient.RemoveVm(cmd.Context(), &manager.RemoveReq{CvmId: args[0]})
|
||||
if err != nil {
|
||||
printError(cmd, "Error removing virtual machine: %v ❌ ", err)
|
||||
return
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc/agent"
|
||||
managergrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/manager"
|
||||
@@ -22,12 +23,14 @@ type CLI struct {
|
||||
client grpc.Client
|
||||
managerClient manager.ManagerServiceClient
|
||||
connectErr error
|
||||
measurement cmdconfig.MeasurementProvider
|
||||
}
|
||||
|
||||
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig) *CLI {
|
||||
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig, measurement cmdconfig.MeasurementProvider) *CLI {
|
||||
return &CLI{
|
||||
agentConfig: agentConfig,
|
||||
managerConfig: managerConfig,
|
||||
measurement: measurement,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+122
-14
@@ -4,6 +4,11 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
@@ -16,16 +21,20 @@ import (
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/api"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
cvmapi "github.com/ultravioletrs/cocos/agent/cvms/api/grpc"
|
||||
cvmsapi "github.com/ultravioletrs/cocos/agent/cvms/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms/server"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
agentlogger "github.com/ultravioletrs/cocos/internal/logger"
|
||||
attestationconfig "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
cvmgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
|
||||
cvmsgrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/cvm"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -34,10 +43,15 @@ const (
|
||||
defSvcGRPCPort = "7002"
|
||||
retryInterval = 5 * time.Second
|
||||
envPrefixCVMGRPC = "AGENT_CVM_GRPC_"
|
||||
storageDir = "/var/lib/cocos/agent"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"debug"`
|
||||
Vmpl int `env:"AGENT_VMPL" envDefault:"2"`
|
||||
AgentGrpcHost string `env:"AGENT_GRPC_HOST" envDefault:"0.0.0.0"`
|
||||
CAUrl string `env:"AGENT_CVM_CA_URL" envDefault:""`
|
||||
CVMId string `env:"AGENT_CVM_ID" envDefault:""`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -71,11 +85,22 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
qp, err := quoteprovider.GetQuoteProvider()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
exitCode = 1
|
||||
return
|
||||
var qp client.LeveledQuoteProvider
|
||||
vtpmAttest := vtpm.Attest
|
||||
|
||||
if !sevGuesDeviceExists() {
|
||||
logger.Info("SEV-SNP device not found")
|
||||
qpMock := new(mocks.LeveledQuoteProvider)
|
||||
qpMock.On("GetRawQuoteAtLevel", mock.Anything, mock.Anything).Return([]uint8{}, errors.New("SEV-SNP device not found"))
|
||||
qp = qpMock
|
||||
vtpmAttest = vtpm.EmptyAttest
|
||||
} else {
|
||||
qp, err = quoteprovider.GetLeveledQuoteProvider()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create quote provider %s", err.Error()))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cvmGrpcConfig := pkggrpc.CVMClientConfig{}
|
||||
@@ -85,7 +110,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
cvmGRPCClient, cvmClient, err := cvmgrpc.NewCVMClient(cvmGrpcConfig)
|
||||
cvmGRPCClient, cvmsClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -93,16 +118,48 @@ func main() {
|
||||
}
|
||||
defer cvmGRPCClient.Close()
|
||||
|
||||
pc, err := cvmClient.Process(ctx)
|
||||
reconnectFn := func(ctx context.Context) (pkggrpc.Client, cvms.Service_ProcessClient, error) {
|
||||
grpcClient, newClient, err := cvmsgrpc.NewCVMClient(cvmGrpcConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Don't defer close here as we want to keep the connection open
|
||||
|
||||
pc, err := newClient.Process(ctx)
|
||||
if err != nil {
|
||||
grpcClient.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
return grpcClient, pc, nil
|
||||
}
|
||||
|
||||
pc, err := cvmsClient.Process(ctx)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
svc := newService(ctx, logger, eventSvc, qp)
|
||||
if cfg.Vmpl < 0 || cfg.Vmpl > 3 {
|
||||
logger.Error("vmpl level must be in a range [0, 3]")
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
mc := cvmapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc))
|
||||
svc := newService(ctx, logger, eventSvc, qp, cfg.Vmpl, vtpmAttest)
|
||||
|
||||
if err := os.MkdirAll(storageDir, 0o755); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create storage directory: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
mc, err := cvmsapi.NewClient(pc, svc, eventsLogsQueue, logger, server.NewServer(logger, svc, cfg.AgentGrpcHost, qp, cfg.CAUrl, cfg.CVMId), storageDir, reconnectFn, cvmGRPCClient)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
ch := make(chan os.Signal, 1)
|
||||
@@ -123,13 +180,29 @@ func main() {
|
||||
return mc.Process(ctx, cancel)
|
||||
})
|
||||
|
||||
attestation, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
eventsLogsQueue <- &cvms.ClientStreamMessage{
|
||||
Message: &cvms.ClientStreamMessage_VTPMattestationReport{
|
||||
VTPMattestationReport: &cvms.AttestationResponse{
|
||||
File: attestation,
|
||||
CertSerialNumber: certSerialNumber,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
}
|
||||
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.QuoteProvider) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, qp)
|
||||
func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Service, qp client.LeveledQuoteProvider, vmpl int, vtpmAttest vtpm.VtpmAttest) agent.Service {
|
||||
svc := agent.New(ctx, logger, eventSvc, qp, vmpl, vtpmAttest)
|
||||
|
||||
svc = api.LoggingMiddleware(svc, logger)
|
||||
counter, latency := prometheus.MakeMetrics(svcName, "api")
|
||||
@@ -137,3 +210,38 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func sevGuesDeviceExists() bool {
|
||||
d, err := client.OpenDevice()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
d.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
|
||||
if certFilePath == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
certFile, err := os.ReadFile(certFilePath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
certPem, _ := pem.Decode(certFile)
|
||||
certx509, err := x509.ParseCertificate(certPem.Bytes)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
nonceSNP := sha512.Sum512(certFile)
|
||||
nonceVTPM := sha256.Sum256(certFile)
|
||||
attestation, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestationconfig.SNPvTPM)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return attestation, certx509.SerialNumber.String(), nil
|
||||
}
|
||||
|
||||
+15
-2
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/ultravioletrs/cocos/cli"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
cmd "github.com/virtee/sev-snp-measure-go/sevsnpmeasure/cmd"
|
||||
)
|
||||
@@ -28,7 +29,8 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
|
||||
LogLevel string `env:"AGENT_LOG_LEVEL" envDefault:"info"`
|
||||
IgvmBinaryPath string `env:"IGVM_BINARY_PATH" envDefault:"./build/igvmmeasure"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -106,7 +108,15 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
cliSVC := cli.New(agentGRPCConfig, managerGRPCConfig)
|
||||
options := cmdconfig.IgvmMeasureOptions
|
||||
measurement, err := cmdconfig.NewCmdConfig(cfg.IgvmBinaryPath, options, os.Stderr)
|
||||
if err != nil {
|
||||
message := color.New(color.FgRed).Sprintf("failed to initialize measurement: %s", err) // Use %s instead of %w
|
||||
rootCmd.Println(message)
|
||||
return
|
||||
}
|
||||
|
||||
cliSVC := cli.New(agentGRPCConfig, managerGRPCConfig, measurement)
|
||||
|
||||
if err := cliSVC.InitializeAgentSDK(rootCmd); err == nil {
|
||||
defer cliSVC.Close()
|
||||
@@ -136,6 +146,7 @@ func main() {
|
||||
|
||||
// measure.
|
||||
rootCmd.AddCommand(cmd.NewRootCmd())
|
||||
rootCmd.AddCommand(cliSVC.NewMeasureCmd(cfg.IgvmBinaryPath))
|
||||
|
||||
// Flags
|
||||
keysCmd.PersistentFlags().StringVarP(
|
||||
@@ -149,6 +160,8 @@ func main() {
|
||||
// Attestation Policy commands
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewAddHostDataCmd())
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewGCPAttestationPolicy())
|
||||
attestationPolicyCmd.AddCommand(cliSVC.NewDownloadGCPOvmfFile())
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
logErrorCmd(*rootCmd, err)
|
||||
|
||||
+7
-5
@@ -42,7 +42,9 @@ type config struct {
|
||||
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:4318"`
|
||||
TraceRatio float64 `env:"COCOS_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
|
||||
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build"`
|
||||
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build/attestation_policy"`
|
||||
IgvmMeasureBinary string `env:"MANAGER_IGVMMEASURE_BINARY" envDefault:"../../build/igvmmeasure"`
|
||||
PcrValues string `env:"MANAGER_PCR_VALUES" envDefault:""`
|
||||
EosVersion string `env:"MANAGER_EOS_VERSION" envDefault:""`
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, cfg.EosVersion)
|
||||
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, cfg.EosVersion)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -110,7 +112,7 @@ func main() {
|
||||
manager.RegisterManagerServiceServer(srv, managergrpc.NewServer(svc))
|
||||
}
|
||||
|
||||
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, nil)
|
||||
gs := grpcserver.New(ctx, cancel, svcName, managerGRPCConfig, registerManagerServiceServer, logger, nil, nil, "", "")
|
||||
|
||||
g.Go(func() error {
|
||||
return gs.Start()
|
||||
@@ -125,8 +127,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, eosVersion string) (manager.Service, error) {
|
||||
svc, err := manager.New(qemuCfg, attestationPolicyPath, logger, qemu.NewVM, eosVersion)
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, eosVersion string) (manager.Service, error) {
|
||||
svc, err := manager.New(qemuCfg, attestationPolicyPath, igvmMeasurementBinaryPath, pcrValuesFilePath, logger, qemu.NewVM, eosVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+3
-1
@@ -7,6 +7,8 @@ COCOS_JAEGER_TRACE_RATIO=1.0
|
||||
# Manager Service Configuration
|
||||
MANAGER_INSTANCE_ID=
|
||||
MANAGER_ATTESTATION_POLICY_BINARY=../../build
|
||||
MANAGER_IGVMMEASURE_BINARY=../../build
|
||||
MANAGER_PCR_VALUES=/etc/cocos/pcr_values.json
|
||||
MANAGER_GRPC_CLIENT_CERT=
|
||||
MANAGER_GRPC_CLIENT_KEY=
|
||||
MANAGER_GRPC_SERVER_CA_CERTS=
|
||||
@@ -49,13 +51,13 @@ MANAGER_QEMU_BIN_PATH=qemu-system-x86_64
|
||||
MANAGER_QEMU_USE_SUDO=true
|
||||
MANAGER_QEMU_ENABLE_SEV=false
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=false
|
||||
MANAGER_QEMU_IGVM_FILE=/etc/cocos/coconut-qemu.igvm
|
||||
MANAGER_QEMU_ENABLE_KVM=true
|
||||
MANAGER_QEMU_MACHINE=q35
|
||||
MANAGER_QEMU_CPU=EPYC
|
||||
MANAGER_QEMU_SMP_COUNT=4
|
||||
MANAGER_QEMU_SMP_MAXCPUS=16
|
||||
MANAGER_QEMU_MEM_ID=ram1
|
||||
MANAGER_QEMU_KERNEL_HASH=false
|
||||
MANAGER_QEMU_NO_GRAPHIC=true
|
||||
MANAGER_QEMU_MONITOR=pty
|
||||
MANAGER_QEMU_HOST_FWD_RANGE=6100-6200
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module github.com/ultravioletrs/cocos
|
||||
|
||||
go 1.23.0
|
||||
go 1.23.2
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/absmach/magistrala v0.15.1
|
||||
@@ -8,30 +10,59 @@ require (
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/go-sev-guest v0.12.1
|
||||
github.com/google/go-sev-guest v0.13.0
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 // indirect
|
||||
github.com/mdlayher/vsock v1.2.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/virtee/sev-snp-measure-go v0.0.0-20240530153610-e6e8dc9b6877
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0
|
||||
go.opentelemetry.io/otel/trace v1.34.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/sync v0.10.0
|
||||
google.golang.org/grpc v1.69.4
|
||||
google.golang.org/protobuf v1.36.3
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0
|
||||
go.opentelemetry.io/otel/trace v1.35.0
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/sync v0.12.0
|
||||
google.golang.org/grpc v1.71.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.51.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/gce-tcb-verifier v0.3.1
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.19.2 // indirect
|
||||
cloud.google.com/go v0.118.3 // indirect
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.4.1 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.3.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.2 // indirect
|
||||
github.com/google/go-attestation v0.5.1 // indirect
|
||||
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
@@ -39,47 +70,63 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/api v0.224.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/absmach/certs v0.0.0-20250313105043-afbcda9a9963
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/docker v27.5.1+incompatible
|
||||
github.com/docker/docker v28.0.4+incompatible
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/go-configfs-tsm v0.2.2 // indirect
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
|
||||
github.com/google/go-tpm v0.9.3
|
||||
github.com/google/go-tpm-tools v0.4.4
|
||||
github.com/google/logger v1.1.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||
github.com/prometheus/client_golang v1.21.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.59.1 // indirect
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/term v0.30.0
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/virtee/sev-snp-measure-go => github.com/sammyoina/sev-snp-measure-go v0.0.0-20241202151803-ef189f0ff825
|
||||
|
||||
replace github.com/google/go-tpm-tools => github.com/danko-miladinovic/go-tpm-tools v0.0.0-20250228160324-1ebcfd79567c
|
||||
|
||||
+143
-8
@@ -11,14 +11,16 @@ users:
|
||||
- docker # Add cocos user to the docker group
|
||||
sudo:
|
||||
- ALL=(ALL:ALL) ALL
|
||||
shell: /sbin/nologin
|
||||
- name: algo_user
|
||||
gecos: Algorithm User
|
||||
groups:
|
||||
- docker # Add to docker group for container execution
|
||||
shell: /bin/bash
|
||||
|
||||
chpasswd:
|
||||
list: |
|
||||
cocos_user:password
|
||||
expire: False
|
||||
|
||||
ssh_pwauth: True
|
||||
sudo:
|
||||
- /usr/bin/python3 # Allow running Python
|
||||
- /home/algo_user/.wasmtime/bin/wasmtime # Allow running Wasmtime
|
||||
- /usr/bin/docker # Allow running Docker commands
|
||||
|
||||
packages:
|
||||
- curl
|
||||
@@ -27,6 +29,8 @@ packages:
|
||||
- python3
|
||||
- python3-dev
|
||||
- net-tools # Add net-tools to install the 'route' command
|
||||
- iptables-persistent # Add iptables-persistent to save firewall rules
|
||||
- sudo # Ensure sudo is installed
|
||||
|
||||
write_files:
|
||||
- path: /etc/cocos/certs/cert.pem
|
||||
@@ -134,6 +138,102 @@ write_files:
|
||||
exec /bin/cocos-agent
|
||||
permissions: "0755"
|
||||
|
||||
# Firewall configuration script
|
||||
- path: /cocos_init/setup_firewall.sh
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
# Flush existing rules
|
||||
iptables -F
|
||||
iptables -X
|
||||
iptables -t nat -F
|
||||
iptables -t nat -X
|
||||
iptables -t mangle -F
|
||||
iptables -t mangle -X
|
||||
|
||||
# Set default policies to DROP
|
||||
iptables -P INPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -P OUTPUT ACCEPT
|
||||
|
||||
# Allow loopback interface
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Allow established and related connections
|
||||
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Allow port 7002 (TCP) for incoming connections
|
||||
iptables -A INPUT -p tcp --dport 7002 -j ACCEPT
|
||||
|
||||
# Save rules to persist after reboot
|
||||
mkdir -p /etc/iptables
|
||||
iptables-save > /etc/iptables/rules.v4
|
||||
|
||||
echo "Firewall configured to allow only port 7002"
|
||||
permissions: "0755"
|
||||
|
||||
# Setup script for algo_user
|
||||
- path: /cocos_init/setup_algo_user.sh
|
||||
content: |
|
||||
#!/bin/bash
|
||||
|
||||
# Create datasets directory with the right permissions
|
||||
mkdir -p /datasets
|
||||
chown root:root /datasets
|
||||
chmod 755 /datasets
|
||||
|
||||
# Create a home directory for algo_user
|
||||
mkdir -p /home/algo_user
|
||||
chown algo_user:algo_user /home/algo_user
|
||||
chmod 750 /home/algo_user
|
||||
|
||||
# Set up Wasmtime for algo_user
|
||||
mkdir -p /home/algo_user/.wasmtime/bin
|
||||
|
||||
# Link system Wasmtime to user's directory
|
||||
ln -s /root/.wasmtime/bin/wasmtime /home/algo_user/.wasmtime/bin/wasmtime
|
||||
chown -R algo_user:algo_user /home/algo_user/.wasmtime
|
||||
|
||||
# Configure sudoers for algo_user
|
||||
cat > /etc/sudoers.d/algo_user << EOF
|
||||
algo_user ALL=(ALL) NOPASSWD: /usr/bin/python3
|
||||
algo_user ALL=(ALL) NOPASSWD: /home/algo_user/.wasmtime/bin/wasmtime
|
||||
algo_user ALL=(ALL) NOPASSWD: /usr/bin/docker
|
||||
Defaults:algo_user !requiretty
|
||||
EOF
|
||||
|
||||
chmod 440 /etc/sudoers.d/algo_user
|
||||
|
||||
# Create a .bashrc file that restricts navigation to /datasets
|
||||
cat > /home/algo_user/.bashrc << EOF
|
||||
# .bashrc for algo_user
|
||||
|
||||
# Restrict navigation outside /datasets
|
||||
function cd() {
|
||||
if [[ "\$1" == /* && "\$1" != "/datasets"* && "\$1" != "/home/algo_user"* ]]; then
|
||||
echo "Access denied: You can only access files in /datasets or your home directory"
|
||||
return 1
|
||||
else
|
||||
builtin cd "\$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Set PATH to include necessary binaries
|
||||
export PATH=/usr/bin:/bin:/usr/local/bin:/home/algo_user/.wasmtime/bin
|
||||
|
||||
# Set default directory
|
||||
if [[ \$PWD != "/datasets"* && \$PWD != "/home/algo_user"* ]]; then
|
||||
cd /datasets
|
||||
fi
|
||||
EOF
|
||||
|
||||
chown algo_user:algo_user /home/algo_user/.bashrc
|
||||
chmod 644 /home/algo_user/.bashrc
|
||||
|
||||
echo "algo_user setup completed successfully"
|
||||
permissions: "0755"
|
||||
|
||||
runcmd:
|
||||
# Create necessary directories
|
||||
- mkdir -p /cocos
|
||||
@@ -163,6 +263,41 @@ runcmd:
|
||||
- sh ./get-docker.sh && echo "Docker installed successfully" || echo "Failed to install Docker"
|
||||
- usermod -aG docker cocos_user && echo "Added cocos_user to the docker group" || echo "Failed to add cocos_user to the docker group"
|
||||
|
||||
# Disable SSH service
|
||||
- echo "Disabling SSH service..."
|
||||
- systemctl disable ssh.service && echo "SSH service disabled successfully" || echo "Failed to disable SSH service"
|
||||
- systemctl disable sshd.service && echo "SSHD service disabled successfully" || echo "Failed to disable SSHD service"
|
||||
- systemctl stop ssh.service && echo "SSH service stopped successfully" || echo "Failed to stop SSH service"
|
||||
- systemctl stop sshd.service && echo "SSHD service stopped successfully" || echo "Failed to stop SSHD service"
|
||||
|
||||
# Completely remove SSH server
|
||||
- echo "Removing SSH server..."
|
||||
- apt-get purge -y openssh-server && echo "SSH server removed successfully" || echo "Failed to remove SSH server"
|
||||
- apt-get autoremove -y && echo "Removed dependencies successfully" || echo "Failed to remove dependencies"
|
||||
|
||||
# Set up firewall to only allow port 7002
|
||||
- echo "Setting up firewall..."
|
||||
- bash /cocos_init/setup_firewall.sh && echo "Firewall configured successfully" || echo "Failed to configure firewall"
|
||||
|
||||
# Ensure iptables rules persist after reboot
|
||||
- echo "Ensuring iptables rules persist after reboot..."
|
||||
- systemctl enable netfilter-persistent && echo "netfilter-persistent service enabled successfully" || echo "Failed to enable netfilter-persistent service"
|
||||
- systemctl start netfilter-persistent && echo "netfilter-persistent service started successfully" || echo "Failed to start netfilter-persistent service"
|
||||
|
||||
# Set up algo_user environment
|
||||
- echo "Setting up algo_user environment..."
|
||||
- bash /cocos_init/setup_algo_user.sh && echo "algo_user environment setup successfully" || echo "Failed to set up algo_user environment"
|
||||
|
||||
# Create datasets directory and set permissions
|
||||
- echo "Creating datasets directory..."
|
||||
- mkdir -p /datasets && echo "datasets directory created successfully" || echo "Failed to create datasets directory"
|
||||
- chown root:root /datasets && echo "datasets ownership set successfully" || echo "Failed to set datasets ownership"
|
||||
- chmod 755 /datasets && echo "datasets permissions set successfully" || echo "Failed to set datasets permissions"
|
||||
|
||||
# Add algo_user to the docker group
|
||||
- echo "Adding algo_user to the docker group..."
|
||||
- usermod -aG docker algo_user && echo "Added algo_user to the docker group" || echo "Failed to add algo_user to the docker group"
|
||||
|
||||
# Reload systemd and enable the service
|
||||
- echo "[ COCOS AGENT SETUP ] Reloading systemd daemon..."
|
||||
- systemctl daemon-reload && echo "[ COCOS AGENT SETUP ] Systemd daemon reloaded successfully" || echo "[ COCOS AGENT SETUP ] Failed to reload systemd daemon"
|
||||
@@ -171,4 +306,4 @@ runcmd:
|
||||
- echo "[ COCOS AGENT SETUP ] Starting cocos-agent.service..."
|
||||
- systemctl start cocos-agent.service && echo "[ COCOS AGENT SETUP ] cocos-agent.service started successfully" || echo "[ COCOS AGENT SETUP ] Failed to start cocos-agent.service"
|
||||
|
||||
final_message: "Cocos agent setup complete. Verify logs to confirm successful service startup."
|
||||
final_message: "Cocos agent and algo_user setup complete. Verify logs to confirm successful service startup."
|
||||
|
||||
@@ -71,3 +71,8 @@ CONFIG_NET_9P_VIRTIO=y
|
||||
CONFIG_9P_FS=y
|
||||
CONFIG_9P_FS_POSIX_ACL=y
|
||||
CONFIG_9P_FS_SECURITY=y
|
||||
|
||||
# TCG TPM
|
||||
CONFIG_TCG_TPM=y
|
||||
CONFIG_TCG_TPM2_HMAC=y
|
||||
CONFIG_TCG_PLATFORM=y
|
||||
|
||||
@@ -27,14 +27,14 @@ BR2_ROOTFS_POST_SCRIPT_ARGS="$(BR2_DEFCONFIG)"
|
||||
# Linux headers same as kernel
|
||||
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_11=y
|
||||
BR2_TOOLCHAIN_HEADERS_LATEST=y
|
||||
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.12-rc6"
|
||||
BR2_TOOLCHAIN_HEADERS_AT_LEAST="6.11-rc7"
|
||||
|
||||
# Kernel
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_GIT=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/torvalds/linux.git"
|
||||
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="v6.12-rc6"
|
||||
BR2_LINUX_KERNEL_VERSION="v6.12-rc6"
|
||||
BR2_LINUX_KERNEL_CUSTOM_REPO_URL="https://github.com/coconut-svsm/linux.git"
|
||||
BR2_LINUX_KERNEL_CUSTOM_REPO_VERSION="svsm"
|
||||
BR2_LINUX_KERNEL_VERSION="svsm"
|
||||
BR2_LINUX_KERNEL_PATCH=""
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/linux.config"
|
||||
|
||||
+153
-17
@@ -4,6 +4,7 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
@@ -11,20 +12,29 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
certs "github.com/absmach/certs"
|
||||
certscli "github.com/absmach/certs/cli"
|
||||
"github.com/absmach/certs/errors"
|
||||
certssdk "github.com/absmach/certs/sdk"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -51,16 +61,22 @@ type Server struct {
|
||||
server.BaseServer
|
||||
server *grpc.Server
|
||||
registerService serviceRegister
|
||||
quoteProvider client.QuoteProvider
|
||||
quoteProvider client.LeveledQuoteProvider
|
||||
authSvc auth.Authenticator
|
||||
health *health.Server
|
||||
caUrl string
|
||||
cvmId string
|
||||
}
|
||||
|
||||
type csrReq struct {
|
||||
CSR string `json:"csr,omitempty"`
|
||||
}
|
||||
|
||||
type serviceRegister func(srv *grpc.Server)
|
||||
|
||||
var _ server.Server = (*Server)(nil)
|
||||
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.QuoteProvider, authSvc auth.Authenticator) server.Server {
|
||||
func New(ctx context.Context, cancel context.CancelFunc, name string, config server.ServerConfiguration, registerService serviceRegister, logger *slog.Logger, qp client.LeveledQuoteProvider, authSvc auth.Authenticator, caUrl string, cvmId string) server.Server {
|
||||
base := config.GetBaseConfig()
|
||||
listenFullAddress := fmt.Sprintf("%s:%s", base.Host, base.Port)
|
||||
return &Server{
|
||||
@@ -75,6 +91,8 @@ func New(ctx context.Context, cancel context.CancelFunc, name string, config ser
|
||||
registerService: registerService,
|
||||
quoteProvider: qp,
|
||||
authSvc: authSvc,
|
||||
caUrl: caUrl,
|
||||
cvmId: cvmId,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +112,7 @@ func (s *Server) Start() error {
|
||||
var listener net.Listener
|
||||
|
||||
if agCfg, ok := s.Config.(server.AgentConfig); ok && agCfg.AttestedTLS {
|
||||
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS()
|
||||
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.caUrl, s.cvmId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
@@ -257,33 +275,99 @@ func loadX509KeyPair(certfile, keyfile string) (tls.Certificate, error) {
|
||||
return tls.X509KeyPair(cert, key)
|
||||
}
|
||||
|
||||
func generateCertificatesForATLS() ([]byte, []byte, error) {
|
||||
func generateCertificatesForATLS(caUrl string, cvmId string) ([]byte, []byte, error) {
|
||||
curve := elliptic.P256()
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate private/public key: %w", err)
|
||||
}
|
||||
|
||||
certTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(202403311),
|
||||
Subject: pkix.Name{
|
||||
var certDERBytes []byte
|
||||
|
||||
if caUrl == "" || cvmId == "" {
|
||||
certTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(202403311),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{organization},
|
||||
Country: []string{country},
|
||||
Province: []string{province},
|
||||
Locality: []string{locality},
|
||||
StreetAddress: []string{streetAddress},
|
||||
PostalCode: []string{postalCode},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(notAfterYear, notAfterMonth, notAfterDay),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
DERBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
|
||||
}
|
||||
|
||||
certDERBytes = DERBytes
|
||||
} else {
|
||||
csrmd := certs.CSRMetadata{
|
||||
Organization: []string{organization},
|
||||
Country: []string{country},
|
||||
Province: []string{province},
|
||||
Locality: []string{locality},
|
||||
StreetAddress: []string{streetAddress},
|
||||
PostalCode: []string{postalCode},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(notAfterYear, notAfterMonth, notAfterDay),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
}
|
||||
|
||||
certDERBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create certificate: %w", err)
|
||||
csr, err := certscli.CreateCSR(csrmd, privateKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create CSR: %w", err)
|
||||
}
|
||||
|
||||
csrData := string(csr.CSR)
|
||||
|
||||
r := csrReq{
|
||||
CSR: csrData,
|
||||
}
|
||||
|
||||
data, error := json.Marshal(r)
|
||||
if error != nil {
|
||||
return nil, nil, errors.NewSDKError(error)
|
||||
}
|
||||
|
||||
notBefore := time.Now()
|
||||
notAfter := time.Now().AddDate(notAfterYear, notAfterMonth, notAfterDay)
|
||||
ttlString := notAfter.Sub(notBefore).String()
|
||||
|
||||
query := url.Values{}
|
||||
query.Add("ttl", ttlString)
|
||||
query_string := query.Encode()
|
||||
|
||||
certsEndpoint := "certs"
|
||||
csrEndpoint := "csrs"
|
||||
endpoint := fmt.Sprintf("%s/%s/%s", certsEndpoint, csrEndpoint, cvmId)
|
||||
|
||||
url := fmt.Sprintf("%s/%s?%s", caUrl, endpoint, query_string)
|
||||
|
||||
_, body, sdkerr := processRequest(http.MethodPost, url, data, nil, http.StatusOK)
|
||||
if sdkerr != nil {
|
||||
return nil, nil, errors.NewSDKError(sdkerr)
|
||||
}
|
||||
|
||||
var cert certssdk.Certificate
|
||||
if err := json.Unmarshal(body, &cert); err != nil {
|
||||
return nil, nil, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
cleanCertificateString := strings.ReplaceAll(cert.Certificate, "\\n", "\n")
|
||||
|
||||
block, rest := pem.Decode([]byte(cleanCertificateString))
|
||||
|
||||
if len(rest) != 0 {
|
||||
return nil, nil, fmt.Errorf("failed to convert generated certificate to DER format: %s", cleanCertificateString)
|
||||
}
|
||||
|
||||
certDERBytes = block.Bytes
|
||||
}
|
||||
|
||||
certBytes := pem.EncodeToMemory(&pem.Block{
|
||||
@@ -301,5 +385,57 @@ func generateCertificatesForATLS() ([]byte, []byte, error) {
|
||||
Bytes: privateKeyBytes,
|
||||
})
|
||||
|
||||
cert, err := x509.ParseCertificate(certDERBytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
|
||||
pubKeyDER, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to marshal public key to DER format: %w", err)
|
||||
}
|
||||
|
||||
if err := vtpm.ExtendPCR(vtpm.PCR15, pubKeyDER); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to extend vTPM PCR with public key: %w", err)
|
||||
}
|
||||
|
||||
return certBytes, keyBytes, nil
|
||||
}
|
||||
|
||||
func processRequest(method, reqUrl string, data []byte, headers map[string]string, expectedRespCodes ...int) (http.Header, []byte, errors.SDKError) {
|
||||
req, err := http.NewRequest(method, reqUrl, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return make(http.Header), []byte{}, errors.NewSDKError(err)
|
||||
}
|
||||
|
||||
// Sets a default value for the Content-Type.
|
||||
// Overridden if Content-Type is passed in the headers arguments.
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return make(http.Header), []byte{}, errors.NewSDKError(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
sdkerr := errors.CheckError(resp, expectedRespCodes...)
|
||||
if sdkerr != nil {
|
||||
return make(http.Header), []byte{}, sdkerr
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return make(http.Header), []byte{}, errors.NewSDKError(err)
|
||||
}
|
||||
return resp.Header, body, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
authmocks "github.com/ultravioletrs/cocos/agent/auth/mocks"
|
||||
"github.com/ultravioletrs/cocos/internal/server"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
@@ -30,6 +31,28 @@ const bufSize = 1024 * 1024
|
||||
|
||||
var lis *bufconn.Listener
|
||||
|
||||
type DummyRWC struct{}
|
||||
|
||||
// Read fills p with byte(len(p)) and returns len(p).
|
||||
func (l *DummyRWC) Read(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
// Fill each byte in p with the value of n as a byte.
|
||||
for i := range p {
|
||||
p[i] = byte(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write simply returns len(p) indicating that all bytes were written.
|
||||
func (l *DummyRWC) Write(p []byte) (int, error) {
|
||||
// In this simple implementation, we ignore the data.
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (l *DummyRWC) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
lis = bufconn.Listen(bufSize)
|
||||
}
|
||||
@@ -47,10 +70,10 @@ func TestNew(t *testing.T) {
|
||||
},
|
||||
}
|
||||
logger := slog.Default()
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
|
||||
assert.NotNil(t, srv)
|
||||
assert.IsType(t, &Server{}, srv)
|
||||
@@ -97,10 +120,10 @@ func TestServerStartWithTLSFile(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@@ -144,10 +167,10 @@ func TestServerStartWithmTLSFile(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
@@ -184,10 +207,10 @@ func TestServerStop(t *testing.T) {
|
||||
}
|
||||
buf := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
|
||||
go func() {
|
||||
err := srv.Start()
|
||||
@@ -259,6 +282,8 @@ func (b *ThreadSafeBuffer) String() string {
|
||||
}
|
||||
|
||||
func TestServerInitializationAndStartup(t *testing.T) {
|
||||
vtpm.ExternalTPM = &DummyRWC{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
config server.AgentConfig
|
||||
@@ -374,10 +399,10 @@ func TestServerInitializationAndStartup(t *testing.T) {
|
||||
|
||||
logBuffer := &ThreadSafeBuffer{}
|
||||
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
|
||||
qp := new(mocks.QuoteProvider)
|
||||
qp := new(mocks.LeveledQuoteProvider)
|
||||
authSvc := new(authmocks.Authenticator)
|
||||
|
||||
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, qp, authSvc)
|
||||
srv := New(ctx, cancel, "TestServer", tc.config, func(srv *grpc.Server) {}, logger, qp, authSvc, "", "")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -20,7 +20,7 @@ func (_m *Server) EXPECT() *Server_Expecter {
|
||||
return &Server_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Start provides a mock function with given fields:
|
||||
// Start provides a mock function with no fields
|
||||
func (_m *Server) Start() error {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -65,7 +65,7 @@ func (_c *Server_Start_Call) RunAndReturn(run func() error) *Server_Start_Call {
|
||||
return _c
|
||||
}
|
||||
|
||||
// Stop provides a mock function with given fields:
|
||||
// Stop provides a mock function with no fields
|
||||
func (_m *Server) Stop() error {
|
||||
ret := _m.Called()
|
||||
|
||||
|
||||
+108
-126
@@ -6,62 +6,66 @@ Manager service provides a barebones gRPC API and Service interface implementati
|
||||
|
||||
The service is configured using the environment variables from the following table. Note that any unset variables will be replaced with their default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| COCOS_JAEGER_URL | The URL for the Jaeger tracing endpoint. | http://localhost:4318 |
|
||||
| COCOS_JAEGER_TRACE_RATIO | The ratio of traces to sample. | 1.0 |
|
||||
| MANAGER_INSTANCE_ID | The instance ID for the manager service. | |
|
||||
| MANAGER_ATTESTATION_POLICY_BINARY | The file path for the attestation policy binary. | ../../build |
|
||||
| MANAGER_GRPC_CLIENT_CERT | The file path for the client certificate. | |
|
||||
| MANAGER_GRPC_CLIENT_KEY | The file path for the client private key. | |
|
||||
| MANAGER_GRPC_SERVER_CA_CERTS | The file path for the server CA certificate(s). | |
|
||||
| MANAGER_GRPC_URL | The URL for the gRPC endpoint. | localhost:7001 |
|
||||
| MANAGER_GRPC_TIMEOUT | The timeout for gRPC requests. | 60s |
|
||||
| MANAGER_EOS_VERSION | The EOS version used for booting SVMs. | |
|
||||
| MANAGER_INSTANCE_ID | Manager service instance ID | |
|
||||
| MANAGER_QEMU_MEMORY_SIZE | The total memory size for the virtual machine. Can be specified in a human-readable format like "2048M" or "4G". | 2048M |
|
||||
| MANAGER_QEMU_MEMORY_SLOTS | The number of memory slots for the virtual machine. | 5 |
|
||||
| MANAGER_QEMU_MAX_MEMORY | The maximum memory size for the virtual machine. Can be specified in a human-readable format like "30G". | 30G |
|
||||
| MANAGER_QEMU_OVMF_CODE_IF | The interface type for the OVMF code. | pflash |
|
||||
| MANAGER_QEMU_OVMF_CODE_FORMAT | The format of the OVMF code file. | raw |
|
||||
| MANAGER_QEMU_OVMF_CODE_UNIT | The unit number for the OVMF code. | 0 |
|
||||
| MANAGER_QEMU_OVMF_CODE_FILE | The file path for the OVMF code. | /usr/share/OVMF/OVMF_CODE.fd |
|
||||
| MANAGER_QEMU_OVMF_VERSION | The version number of EDKII from which OVMF was built | edk2-stable202408 |
|
||||
| MANAGER_QEMU_OVMF_CODE_READONLY | Whether the OVMF code should be read-only. | on |
|
||||
| MANAGER_QEMU_OVMF_VARS_IF | The interface type for the OVMF variables. | pflash |
|
||||
| MANAGER_QEMU_OVMF_VARS_FORMAT | The format of the OVMF variables file. | raw |
|
||||
| MANAGER_QEMU_OVMF_VARS_UNIT | The unit number for the OVMF variables. | 1 |
|
||||
| MANAGER_QEMU_OVMF_VARS_FILE | The file path for the OVMF variables. | /usr/share/OVMF/OVMF_VARS.fd |
|
||||
| MANAGER_QEMU_NETDEV_ID | The ID for the network device. | vmnic |
|
||||
| MANAGER_QEMU_HOST_FWD_AGENT | The port number for the host forward agent. | 7020 |
|
||||
| MANAGER_QEMU_GUEST_FWD_AGENT | The port number for the guest forward agent. | 7002 |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_DISABLE_LEGACY | Whether to disable the legacy PCI device. | on |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_IOMMU_PLATFORM | Whether to enable the IOMMU platform for the virtio-net PCI device. | true |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ADDR | The PCI address for the virtio-net PCI device. | 0x2 |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE | The file path for the ROM image for the virtio-net PCI device. | |
|
||||
| MANAGER_QEMU_DISK_IMG_KERNEL_FILE | The file path for the kernel image. | img/bzImage |
|
||||
| MANAGER_QEMU_DISK_IMG_ROOTFS_FILE | The file path for the root filesystem image. | img/rootfs.cpio.gz |
|
||||
| MANAGER_QEMU_SEV_ID | The ID for the Secure Encrypted Virtualization (SEV) device. | sev0 |
|
||||
| MANAGER_QEMU_SEV_CBITPOS | The position of the C-bit in the physical address. | 51 |
|
||||
| MANAGER_QEMU_SEV_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV. | 1 |
|
||||
| MANAGER_QEMU_HOST_DATA | Additional data for the SEV host. | |
|
||||
| MANAGER_QEMU_VSOCK_ID | The ID for the virtual socket device. | vhost-vsock-pci0 |
|
||||
| MANAGER_QEMU_VSOCK_GUEST_CID | The guest-side CID (Context ID) for the virtual socket device. | 3 |
|
||||
| MANAGER_QEMU_VSOCK_VNC | Whether to enable the virtual socket device for VNC. | 0 |
|
||||
| MANAGER_QEMU_BIN_PATH | The file path for the QEMU binary. | qemu-system-x86_64 |
|
||||
| MANAGER_QEMU_USE_SUDO | Whether to use sudo to run QEMU. | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV | Whether to enable Secure Encrypted Virtualization (SEV). | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV_SNP | Whether to enable Secure Nested Paging (SEV-SNP). | true |
|
||||
| MANAGER_QEMU_ENABLE_KVM | Whether to enable the Kernel-based Virtual Machine (KVM) acceleration. | true |
|
||||
| MANAGER_QEMU_MACHINE | The machine type for QEMU. | q35 |
|
||||
| MANAGER_QEMU_CPU | The CPU model for QEMU. | EPYC |
|
||||
| MANAGER_QEMU_SMP_COUNT | The number of virtual CPUs. | 4 |
|
||||
| MANAGER_QEMU_SMP_MAXCPUS | The maximum number of virtual CPUs. | 64 |
|
||||
| MANAGER_QEMU_MEM_ID | The ID for the memory device. | ram1 |
|
||||
| MANAGER_QEMU_KERNEL_HASH | Whether to enable kernel hash verification. | false |
|
||||
| MANAGER_QEMU_NO_GRAPHIC | Whether to disable the graphical display. | true |
|
||||
| MANAGER_QEMU_MONITOR | The type of monitor to use. | pty |
|
||||
| MANAGER_QEMU_HOST_FWD_RANGE | The range of host ports to forward. | 6100-6200 |
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| COCOS_JAEGER_URL | The URL for the Jaeger tracing endpoint. | http://localhost:4318 |
|
||||
| COCOS_JAEGER_TRACE_RATIO | The ratio of traces to sample. | 1.0 |
|
||||
| MANAGER_INSTANCE_ID | The instance ID for the manager service. | |
|
||||
| MANAGER_ATTESTATION_POLICY_BINARY | The file path for the attestation policy binarie. | ../../build/attestation_policy |
|
||||
| MANAGER_IGVMMEASURE_BINARY | The file path for the igvmmeasure binarie. | ../../build/igvmmeasure |
|
||||
| MANAGER_PCR_VALUES | The file path for the file with the expected PCR values. | |
|
||||
| MANAGER_GRPC_CLIENT_CERT | The file path for the client certificate. | |
|
||||
| MANAGER_GRPC_CLIENT_KEY | The file path for the client private key. | |
|
||||
| MANAGER_GRPC_SERVER_CA_CERTS | The file path for the server CA certificate(s). | |
|
||||
| MANAGER_GRPC_URL | The URL for the gRPC endpoint. | localhost:7001 |
|
||||
| MANAGER_GRPC_TIMEOUT | The timeout for gRPC requests. | 60s |
|
||||
| MANAGER_EOS_VERSION | The EOS version used for booting CVMs. | |
|
||||
| MANAGER_INSTANCE_ID | Manager service instance ID | |
|
||||
| MANAGER_QEMU_MEMORY_SIZE | The total memory size for the virtual machine. Can be specified in a human-readable format like "2048M" or "4G". | 2048M |
|
||||
| MANAGER_QEMU_MEMORY_SLOTS | The number of memory slots for the virtual machine. | 5 |
|
||||
| MANAGER_QEMU_MAX_MEMORY | The maximum memory size for the virtual machine. Can be specified in a human-readable format like "30G". | 30G |
|
||||
| MANAGER_QEMU_OVMF_CODE_IF | The interface type for the OVMF code. | pflash |
|
||||
| MANAGER_QEMU_OVMF_CODE_FORMAT | The format of the OVMF code file. | raw |
|
||||
| MANAGER_QEMU_OVMF_CODE_UNIT | The unit number for the OVMF code. | 0 |
|
||||
| MANAGER_QEMU_OVMF_CODE_FILE | The file path for the OVMF code. | /usr/share/OVMF/OVMF_CODE.fd |
|
||||
| MANAGER_QEMU_OVMF_VERSION | The version number of EDKII from which OVMF was built | edk2-stable202408 |
|
||||
| MANAGER_QEMU_OVMF_CODE_READONLY | Whether the OVMF code should be read-only. | on |
|
||||
| MANAGER_QEMU_OVMF_VARS_IF | The interface type for the OVMF variables. | pflash |
|
||||
| MANAGER_QEMU_OVMF_VARS_FORMAT | The format of the OVMF variables file. | raw |
|
||||
| MANAGER_QEMU_OVMF_VARS_UNIT | The unit number for the OVMF variables. | 1 |
|
||||
| MANAGER_QEMU_OVMF_VARS_FILE | The file path for the OVMF variables. | /usr/share/OVMF/OVMF_VARS.fd |
|
||||
| MANAGER_QEMU_NETDEV_ID | The ID for the network device. | vmnic |
|
||||
| MANAGER_QEMU_HOST_FWD_AGENT | The port number for the host forward agent. | 7020 |
|
||||
| MANAGER_QEMU_GUEST_FWD_AGENT | The port number for the guest forward agent. | 7002 |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_DISABLE_LEGACY | Whether to disable the legacy PCI device. | on |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_IOMMU_PLATFORM | Whether to enable the IOMMU platform for the virtio-net PCI device. | true |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ADDR | The PCI address for the virtio-net PCI device. | 0x2 |
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE | The file path for the ROM image for the virtio-net PCI device. | |
|
||||
| MANAGER_QEMU_DISK_IMG_KERNEL_FILE | The file path for the kernel image. | img/bzImage |
|
||||
| MANAGER_QEMU_DISK_IMG_ROOTFS_FILE | The file path for the root filesystem image. | img/rootfs.cpio.gz |
|
||||
| MANAGER_QEMU_SEV_ID | The ID for the Secure Encrypted Virtualization (SEV) device. | sev0 |
|
||||
| MANAGER_QEMU_SEV_CBITPOS | The position of the C-bit in the physical address. | 51 |
|
||||
| MANAGER_QEMU_SEV_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV. | 1 |
|
||||
| MANAGER_QEMU_ENABLE_HOST_DATA | Enable additional data for the SEV host. | false |
|
||||
| MANAGER_QEMU_HOST_DATA | Additional data for the SEV host. | |
|
||||
| MANAGER_QEMU_IGVM_ID | The ID of the IGVM file. | igvm0 |
|
||||
| MANAGER_QEMU_IGVM_FILE | The file path to the IGVM file. | /root/coconut-qemu.igvm |
|
||||
| MANAGER_QEMU_VSOCK_ID | The ID for the virtual socket device. | vhost-vsock-pci0 |
|
||||
| MANAGER_QEMU_VSOCK_GUEST_CID | The guest-side CID (Context ID) for the virtual socket device. | 3 |
|
||||
| MANAGER_QEMU_VSOCK_VNC | Whether to enable the virtual socket device for VNC. | 0 |
|
||||
| MANAGER_QEMU_BIN_PATH | The file path for the QEMU binary. | qemu-system-x86_64 |
|
||||
| MANAGER_QEMU_USE_SUDO | Whether to use sudo to run QEMU. | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV | Whether to enable Secure Encrypted Virtualization (SEV). | false |
|
||||
| MANAGER_QEMU_ENABLE_SEV_SNP | Whether to enable Secure Nested Paging (SEV-SNP). | true |
|
||||
| MANAGER_QEMU_ENABLE_KVM | Whether to enable the Kernel-based Virtual Machine (KVM) acceleration. | true |
|
||||
| MANAGER_QEMU_MACHINE | The machine type for QEMU. | q35 |
|
||||
| MANAGER_QEMU_CPU | The CPU model for QEMU. | EPYC |
|
||||
| MANAGER_QEMU_SMP_COUNT | The number of virtual CPUs. | 4 |
|
||||
| MANAGER_QEMU_SMP_MAXCPUS | The maximum number of virtual CPUs. | 64 |
|
||||
| MANAGER_QEMU_MEM_ID | The ID for the memory device. | ram1 |
|
||||
| MANAGER_QEMU_NO_GRAPHIC | Whether to disable the graphical display. | true |
|
||||
| MANAGER_QEMU_MONITOR | The type of monitor to use. | pty |
|
||||
| MANAGER_QEMU_HOST_FWD_RANGE | The range of host ports to forward. | 6100-6200 |
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -88,21 +92,11 @@ sudo apt install qemu-kvm
|
||||
|
||||
Create `img` directory in `cmd/manager`.
|
||||
|
||||
#### Add Vsock
|
||||
The necessary kernel modules must be loaded on the hypervisor. To check if `vhost_vsock` is loaded run:
|
||||
```shell
|
||||
lsmod | grep vhost_vsock
|
||||
```
|
||||
#### Virtual filesystem
|
||||
|
||||
If `vhost_vsock` is not loaded run the following commands:
|
||||
9P (or Plan 9 Filesystem) in QEMU is a lightweight, network-based file-sharing protocol. In Cocos, the 9P is used to transfer environment variables and TLS certificates for cloud communication from the Manager to the Agent.
|
||||
|
||||
```shell
|
||||
sudo modprobe vhost_vsock
|
||||
ls -l /dev/vhost-vsock
|
||||
# crw-rw-rw- 1 root kvm 10, 241 Jan 16 12:05 /dev/vhost-vsock
|
||||
ls -l /dev/vsock
|
||||
# crw-rw-rw- 1 root root 10, 121 Jan 16 12:05 /dev/vsock
|
||||
```
|
||||
You should define the environment variables in a file called environment. For the number and meaning of the environment variables, please refer to the Agent [Readme](https://github.com/ultravioletrs/cocos/blob/main/agent/README.md).
|
||||
|
||||
### Prepare Cocos HAL
|
||||
|
||||
@@ -122,10 +116,32 @@ OVMF_CODE=/usr/share/OVMF/OVMF_CODE.fd
|
||||
|
||||
sudo find / -name OVMF_VARS.fd
|
||||
# => /usr/share/OVMF/OVMF_VARS.fd
|
||||
OVMF_VARS=/usr/share/OVMF/OVMF_VARS.fd
|
||||
|
||||
# Create a local copy of OVMF_VARS.
|
||||
cp /usr/share/OVMF/OVMF_VARS.fd .
|
||||
|
||||
# Create a directory for the environment file and the certificates for cloud certificates.
|
||||
mkdir env
|
||||
mkdir certs
|
||||
|
||||
# Enter the env directory and create the environemnt file.
|
||||
cd env
|
||||
touch environment
|
||||
|
||||
# Define Computations endpoint URL for agent.
|
||||
# Make sure the Computation endpoint is running (like Cocos Prism).
|
||||
echo AGENT_CVM_GRPC_URL=localhost:7001 >> ./environment
|
||||
# Define log level for the agent.
|
||||
echo AGENT_LOG_LEVEL=debug >> ./environment
|
||||
|
||||
# Return to cmd/manager
|
||||
cd ..
|
||||
|
||||
OVMF_VARS=./OVMF_VARS.fd
|
||||
KERNEL="img/bzImage"
|
||||
INITRD="img/rootfs.cpio.gz"
|
||||
ENV_PATH=./env
|
||||
CERTH_PATH=./certs
|
||||
|
||||
qemu-system-x86_64 \
|
||||
-enable-kvm \
|
||||
@@ -135,15 +151,19 @@ qemu-system-x86_64 \
|
||||
-m 2048M,slots=5,maxmem=10240M \
|
||||
-no-reboot \
|
||||
-drive if=pflash,format=raw,unit=0,file=$OVMF_CODE,readonly=on \
|
||||
-drive if=pflash,format=raw,unit=1,file=$OVMF_VARS \
|
||||
-netdev user,id=vmnic,hostfwd=tcp::7020-:7002 \
|
||||
-device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= \
|
||||
-device vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=3 -vnc :0 \
|
||||
-kernel $KERNEL \
|
||||
-append "earlyprintk=serial console=ttyS0" \
|
||||
-initrd $INITRD \
|
||||
-nographic \
|
||||
-monitor pty \
|
||||
-monitor unix:monitor,server,nowait
|
||||
-monitor unix:monitor,server,nowait \
|
||||
-fsdev local,id=env_fs,path=$ENV_PATH,security_model=mapped \
|
||||
-device virtio-9p-pci,fsdev=env_fs,mount_tag=env_share \
|
||||
-fsdev local,id=cert_fs,path=$CERTH_PATH,security_model=mapped \
|
||||
-device virtio-9p-pci,fsdev=cert_fs,mount_tag=certs_share
|
||||
```
|
||||
Once the VM is booted press enter and on the login use username `root`.
|
||||
|
||||
@@ -188,10 +208,21 @@ MANAGER_QEMU_OVMF_VARS_FILE=/usr/share/OVMF/OVMF_VARS.fd
|
||||
|
||||
NB: we set environment variables that we will use in the shell process where we run `manager`.
|
||||
|
||||
### Trusted Platform Module (TPM)
|
||||
|
||||
The Trusted Platform Module (TPM) plays a fundamental role in this process by providing a tamper-resistant foundation for cryptographic operations, securing sensitive artifacts, measuring system state, and enabling attestation mechanisms.
|
||||
|
||||
### IGVM
|
||||
|
||||
An IGVM file contains all the necessary information to launch a virtual machine on different virtualization platforms. It includes setup commands for the guest system and verification data to ensure the VM is loaded securely and correctly.
|
||||
|
||||
Cocos uses the [COCONUT-SVSM](https://github.com/coconut-svsm/svsm/blob/main/Documentation/docs/installation/INSTALL.md) for the vTPM. The IGVM file contains the OVMF file and the vTPM.
|
||||
|
||||
## Deployment
|
||||
|
||||
To start the service, execute the following shell script (note a server needs to be running see [here](../test/computations/README.md)):
|
||||
To start the service, execute the following shell script (note a server needs to be running see [here](../test/cvms/README.md)):
|
||||
|
||||
The manager can be started as a *systemd* service or a standalone executable. To start the manager as a systemd service, look at the systemd service script [here](https://github.com/ultravioletrs/cocos/blob/main/init/systemd/cocos-manager.service). The environment variables are defined in the `cocos-manager.env` file. Below are examples of how to start the manager.
|
||||
|
||||
```bash
|
||||
# Download the latest version of the service
|
||||
@@ -203,13 +234,14 @@ cd cocos
|
||||
make manager
|
||||
|
||||
# Set the environment variables and run the service
|
||||
MANAGER_GRPC_URL=localhost:7001
|
||||
MANAGER_GRPC_URL=localhost:7001 \
|
||||
MANAGER_LOG_LEVEL=debug \
|
||||
MANAGER_QEMU_USE_SUDO=false \
|
||||
MANAGER_QEMU_ENABLE_SEV=false \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
|
||||
To enable [AMD SEV](https://www.amd.com/en/developer/sev.html) support, start manager like this
|
||||
|
||||
```sh
|
||||
@@ -221,7 +253,7 @@ MANAGER_QEMU_SEV_CBITPOS=51 \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
To build the OVMF with the kernel hash capability, we must build the AmdSev package of OVMF. The result of the build should be a single `OVMF.fd` file (unlike the regular two OVFM files). The OVMF package is located at `OvmfPkg/AmdSev/AmdSevX64.dsc`.
|
||||
To start SEV-SNP, define the IGVM file that contains the vTPM and the OVMF (combined OVMF_CODE and OVMF_VARS) of the CVM.
|
||||
|
||||
To enable [AMD SEV-SNP](https://www.amd.com/en/developer/sev.html) support, start manager like this
|
||||
|
||||
@@ -232,60 +264,10 @@ MANAGER_QEMU_ENABLE_SEV=false \
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=true \
|
||||
MANAGER_QEMU_SEV_CBITPOS=51 \
|
||||
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
|
||||
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
|
||||
MANAGER_QEMU_IGVM_FILE=<path to IGVM file> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
To include the kernel hash into the measurement of the attestation report (SEV or SEV-SNP), start manager like this
|
||||
|
||||
```sh
|
||||
MANAGER_GRPC_URL=localhost:7001 \
|
||||
MANAGER_LOG_LEVEL=debug \
|
||||
MANAGER_QEMU_ENABLE_SEV=false \
|
||||
MANAGER_QEMU_ENABLE_SEV_SNP=true \
|
||||
MANAGER_QEMU_SEV_CBITPOS=51 \
|
||||
MANAGER_QEMU_KERNEL_HASH=true \
|
||||
MANAGER_QEMU_BIN_PATH=<path to QEMU binary> \
|
||||
MANAGER_QEMU_QEMU_OVMF_CODE_FILE=<path to OVMF.fd Amd Sev built package> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
### Verifying VM launch
|
||||
|
||||
NB: To verify that the manager successfully launched the VM, you need to open three terminals on the same machine. In one terminal, you need to launch the computations server by executing (with the environment variables of choice):
|
||||
|
||||
```bash
|
||||
go run ./test/computations/main.go <dataset path> <algo path>
|
||||
```
|
||||
|
||||
and in the second the manager by executing (with the environment variables of choice):
|
||||
|
||||
```bash
|
||||
go run ./cmd/manager/main.go
|
||||
```
|
||||
|
||||
Ensure that the Manager can connect to the Manager test server by setting the MANAGER_GRPC_PORT with the port value of the Manager test server. In the last terminal, you can run the verification commands.
|
||||
|
||||
To verify that the manager launched the VM successfully, run the following command:
|
||||
|
||||
```sh
|
||||
ps aux | grep qemu-system-x86_64
|
||||
```
|
||||
|
||||
You should get something similar to this
|
||||
```
|
||||
darko 324763 95.3 6.0 6398136 981044 ? Sl 16:17 0:15 /usr/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -nographic -monitor pty
|
||||
```
|
||||
|
||||
If you run a command as `sudo`, you should get the output similar to this one
|
||||
|
||||
```
|
||||
root 37982 0.0 0.0 9444 4572 pts/0 S+ 16:18 0:00 sudo /usr/local/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1 -machine memory-encryption=sev0 -nographic -monitor pty
|
||||
root 37989 122 13.1 5345816 4252312 pts/0 Sl+ 16:19 0:04 /usr/local/bin/qemu-system-x86_64 -enable-kvm -machine q35 -cpu EPYC -smp 4,maxcpus=64 -m 4096M,slots=5,maxmem=30G -drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on -drive if=pflash,format=raw,unit=1,file=img/OVMF_VARS.fd -device virtio-scsi-pci,id=scsi,disable-legacy=on,iommu_platform=true -drive file=img/focal-server-cloudimg-amd64.img,if=none,id=disk0,format=qcow2 -device scsi-hd,drive=disk0 -netdev user,id=vmnic,hostfwd=tcp::2222-:22,hostfwd=tcp::9301-:9031,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,romfile= -object sev-guest,id=sev0,cbitpos=51,reduced-phys-bits=1 -machine memory-encryption=sev0 -nographic -monitor pty
|
||||
```
|
||||
|
||||
The two processes are due to the fact that we run the command `/usr/bin/qemu-system-x86_64` as `sudo`, so there is one process for `sudo` command and the other for `/usr/bin/qemu-system-x86_64`.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If the `ps aux | grep qemu-system-x86_64` give you something like this
|
||||
|
||||
@@ -35,22 +35,22 @@ func (s *grpcServer) CreateVm(ctx context.Context, req *manager.CreateReq) (*man
|
||||
|
||||
return &manager.CreateRes{
|
||||
ForwardedPort: port,
|
||||
SvmId: id,
|
||||
CvmId: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) RemoveVm(ctx context.Context, req *manager.RemoveReq) (*emptypb.Empty, error) {
|
||||
if err := s.svc.RemoveVM(ctx, req.SvmId); err != nil {
|
||||
if err := s.svc.RemoveVM(ctx, req.CvmId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *grpcServer) SVMInfo(ctx context.Context, req *manager.SVMInfoReq) (*manager.SVMInfoRes, error) {
|
||||
ovmf, cpunum, cputype, eosversion := s.svc.ReturnSVMInfo(ctx)
|
||||
func (s *grpcServer) CVMInfo(ctx context.Context, req *manager.CVMInfoReq) (*manager.CVMInfoRes, error) {
|
||||
ovmf, cpunum, cputype, eosversion := s.svc.ReturnCVMInfo(ctx)
|
||||
|
||||
return &manager.SVMInfoRes{
|
||||
return &manager.CVMInfoRes{
|
||||
OvmfVersion: ovmf,
|
||||
CpuNum: int32(cpunum),
|
||||
CpuType: cputype,
|
||||
|
||||
@@ -67,11 +67,11 @@ func (lm *loggingMiddleware) FetchAttestationPolicy(ctx context.Context, cmpId s
|
||||
return lm.svc.FetchAttestationPolicy(ctx, cmpId)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
func (lm *loggingMiddleware) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
defer func(begin time.Time) {
|
||||
message := fmt.Sprintf("Method ReturnSVMInfo for computation took %s to complete", time.Since(begin))
|
||||
message := fmt.Sprintf("Method ReturnCVMInfo for computation took %s to complete", time.Since(begin))
|
||||
lm.logger.Info(message)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.ReturnSVMInfo(ctx)
|
||||
return lm.svc.ReturnCVMInfo(ctx)
|
||||
}
|
||||
|
||||
@@ -59,11 +59,11 @@ func (ms *metricsMiddleware) FetchAttestationPolicy(ctx context.Context, cmpId s
|
||||
return ms.svc.FetchAttestationPolicy(ctx, cmpId)
|
||||
}
|
||||
|
||||
func (ms *metricsMiddleware) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
func (ms *metricsMiddleware) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
defer func(begin time.Time) {
|
||||
ms.counter.With("method", "ReturnSVMInfo").Add(1)
|
||||
ms.latency.With("method", "ReturnSVMInfo").Observe(time.Since(begin).Seconds())
|
||||
ms.counter.With("method", "ReturnCVMInfo").Add(1)
|
||||
ms.latency.With("method", "ReturnCVMInfo").Observe(time.Since(begin).Seconds())
|
||||
}(time.Now())
|
||||
|
||||
return ms.svc.ReturnSVMInfo(ctx)
|
||||
return ms.svc.ReturnCVMInfo(ctx)
|
||||
}
|
||||
|
||||
@@ -7,25 +7,42 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/virtee/sev-snp-measure-go/cpuid"
|
||||
"github.com/virtee/sev-snp-measure-go/guest"
|
||||
"github.com/virtee/sev-snp-measure-go/vmmtypes"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const defGuestFeatures = 0x1
|
||||
|
||||
func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationId string) ([]byte, error) {
|
||||
cmd := exec.Command("sudo", fmt.Sprintf("%s/attestation_policy", ms.attestationPolicyBinaryPath), "--policy", "196608")
|
||||
var stderrBuffer bytes.Buffer
|
||||
options := []string{"--policy", "196608"}
|
||||
|
||||
if ms.pcrValuesFilePath != "" {
|
||||
pcrValues := []string{"--pcr", ms.pcrValuesFilePath}
|
||||
options = append(options, pcrValues...)
|
||||
}
|
||||
|
||||
stderr := bufio.NewWriter(&stderrBuffer)
|
||||
|
||||
attestPolicyCmd, err := cmdconfig.NewCmdConfig("sudo", options, stderr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ms.mu.Lock()
|
||||
vm, exists := ms.vms[computationId]
|
||||
@@ -40,22 +57,15 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
_, err := cmd.Output()
|
||||
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
f, err := os.ReadFile("./attestation_policy.json")
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
var attestationPolicy check.Config
|
||||
|
||||
if err = protojson.Unmarshal(f, &attestationPolicy); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -67,26 +77,50 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
return nil, err
|
||||
}
|
||||
case vmi.Config.EnableSEVSNP:
|
||||
measurement, err = guest.CalcLaunchDigest(guest.SEV_SNP, vmi.Config.SMPCount, uint64(cpuid.CpuSigs[vmi.Config.CPU]), vmi.Config.OVMFCodeConfig.File, vmi.Config.KernelFile, vmi.Config.RootFsFile, strconv.Quote(qemu.KernelCommandLine), defGuestFeatures, "", vmmtypes.QEMU, false, "", 0)
|
||||
stderr := bufio.NewWriter(&stderrBuffer)
|
||||
options := cmdconfig.IgvmMeasureOptions
|
||||
|
||||
igvmMeasurement, err := cmdconfig.NewCmdConfig(ms.igvmMeasurementBinaryPath, options, stderr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outputByte, err := igvmMeasurement.Run(ms.qemuCfg.IGVMConfig.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outputString := string(outputByte)
|
||||
lines := strings.Split(strings.TrimSpace(outputString), "\n")
|
||||
|
||||
if len(lines) == 1 {
|
||||
outputString = strings.TrimSpace(outputString)
|
||||
outputString = strings.ToLower(outputString)
|
||||
} else {
|
||||
return nil, fmt.Errorf("error: %s", outputString)
|
||||
}
|
||||
|
||||
measurement, err = hex.DecodeString(outputString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if measurement != nil {
|
||||
attestationPolicy.Policy.Measurement = measurement
|
||||
attestationPolicy.Config.Policy.Measurement = measurement
|
||||
}
|
||||
|
||||
if vmi.Config.HostData != "" {
|
||||
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.HostData)
|
||||
if vmi.Config.SevConfig.EnableHostData {
|
||||
hostData, err := base64.StdEncoding.DecodeString(vmi.Config.SevConfig.HostData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attestationPolicy.Policy.HostData = hostData
|
||||
attestationPolicy.Config.Policy.HostData = hostData
|
||||
}
|
||||
|
||||
attestationPolicy.Policy.MinimumLaunchTcb = vmi.LaunchTCB
|
||||
attestationPolicy.Config.Policy.MinimumLaunchTcb = vmi.LaunchTCB
|
||||
|
||||
f, err = protojson.Marshal(&attestationPolicy)
|
||||
f, err := json.MarshalIndent(attestationPolicy, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -20,7 +21,7 @@ func CreateDummyAttestationPolicyBinary(t *testing.T, behavior string) string {
|
||||
switch behavior {
|
||||
case "success":
|
||||
content = []byte(`#!/bin/sh
|
||||
echo '{"policy": {"measurement": null, "host_data": null}}' > attestation_policy.json
|
||||
echo '{"pcr_values": {"sha256": null, "sha384": null}, "policy": {"measurement": null, "host_data": null}}'
|
||||
`)
|
||||
case "fail":
|
||||
content = []byte(`#!/bin/sh
|
||||
@@ -57,9 +58,10 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
binaryBehavior: "success",
|
||||
vmConfig: qemu.VMInfo{
|
||||
Config: qemu.Config{
|
||||
EnableSEV: true,
|
||||
SMPCount: 2,
|
||||
CPU: "EPYC",
|
||||
EnableSEV: true,
|
||||
EnableSEVSNP: false,
|
||||
SMPCount: 2,
|
||||
CPU: "EPYC",
|
||||
OVMFCodeConfig: qemu.OVMFCodeConfig{
|
||||
File: "/path/to/OVMF_CODE.fd",
|
||||
},
|
||||
@@ -68,23 +70,6 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
},
|
||||
expectedError: "open /path/to/OVMF_CODE.fd: no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "Valid SEV-SNP configuration",
|
||||
computationId: "sev-snp-computation",
|
||||
binaryBehavior: "success",
|
||||
vmConfig: qemu.VMInfo{
|
||||
Config: qemu.Config{
|
||||
EnableSEVSNP: true,
|
||||
SMPCount: 4,
|
||||
CPU: "EPYC-v2",
|
||||
OVMFCodeConfig: qemu.OVMFCodeConfig{
|
||||
File: "/path/to/OVMF_CODE_SNP.fd",
|
||||
},
|
||||
},
|
||||
LaunchTCB: 0,
|
||||
},
|
||||
expectedError: "open /path/to/OVMF_CODE_SNP.fd: no such file or director",
|
||||
},
|
||||
{
|
||||
name: "Invalid computation ID",
|
||||
computationId: "non-existent",
|
||||
@@ -121,7 +106,7 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
},
|
||||
LaunchTCB: 0,
|
||||
},
|
||||
expectedError: "no such file or directory",
|
||||
expectedError: "failed to decode Attestation Policy file",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -132,7 +117,8 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
|
||||
ms := &managerService{
|
||||
vms: make(map[string]vm.VM),
|
||||
attestationPolicyBinaryPath: tempDir,
|
||||
attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"),
|
||||
pcrValuesFilePath: tempDir,
|
||||
qemuCfg: qemu.Config{
|
||||
CPU: "EPYC",
|
||||
},
|
||||
|
||||
+105
-85
@@ -3,7 +3,7 @@
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.0
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc v5.29.0
|
||||
// source: manager/manager.proto
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,6 +32,8 @@ type CreateReq struct {
|
||||
AgentCvmClientKey []byte `protobuf:"bytes,3,opt,name=agent_cvm_client_key,json=agentCvmClientKey,proto3" json:"agent_cvm_client_key,omitempty"`
|
||||
AgentCvmClientCert []byte `protobuf:"bytes,4,opt,name=agent_cvm_client_cert,json=agentCvmClientCert,proto3" json:"agent_cvm_client_cert,omitempty"`
|
||||
AgentCvmServerUrl string `protobuf:"bytes,5,opt,name=agent_cvm_server_url,json=agentCvmServerUrl,proto3" json:"agent_cvm_server_url,omitempty"`
|
||||
AgentCvmCaUrl string `protobuf:"bytes,6,opt,name=agent_cvm_ca_url,json=agentCvmCaUrl,proto3" json:"agent_cvm_ca_url,omitempty"`
|
||||
Ttl string `protobuf:"bytes,7,opt,name=ttl,proto3" json:"ttl,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -100,10 +103,24 @@ func (x *CreateReq) GetAgentCvmServerUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateReq) GetAgentCvmCaUrl() string {
|
||||
if x != nil {
|
||||
return x.AgentCvmCaUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateReq) GetTtl() string {
|
||||
if x != nil {
|
||||
return x.Ttl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CreateRes struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ForwardedPort string `protobuf:"bytes,1,opt,name=forwarded_port,json=forwardedPort,proto3" json:"forwarded_port,omitempty"`
|
||||
SvmId string `protobuf:"bytes,2,opt,name=svm_id,json=svmId,proto3" json:"svm_id,omitempty"`
|
||||
CvmId string `protobuf:"bytes,2,opt,name=cvm_id,json=cvmId,proto3" json:"cvm_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -145,16 +162,16 @@ func (x *CreateRes) GetForwardedPort() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CreateRes) GetSvmId() string {
|
||||
func (x *CreateRes) GetCvmId() string {
|
||||
if x != nil {
|
||||
return x.SvmId
|
||||
return x.CvmId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RemoveReq struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
SvmId string `protobuf:"bytes,1,opt,name=svm_id,json=svmId,proto3" json:"svm_id,omitempty"`
|
||||
CvmId string `protobuf:"bytes,1,opt,name=cvm_id,json=cvmId,proto3" json:"cvm_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -189,9 +206,9 @@ func (*RemoveReq) Descriptor() ([]byte, []int) {
|
||||
return file_manager_manager_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *RemoveReq) GetSvmId() string {
|
||||
func (x *RemoveReq) GetCvmId() string {
|
||||
if x != nil {
|
||||
return x.SvmId
|
||||
return x.CvmId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -248,7 +265,7 @@ func (x *AttestationPolicyRes) GetId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type SVMInfoRes struct {
|
||||
type CVMInfoRes struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
OvmfVersion string `protobuf:"bytes,2,opt,name=ovmf_version,json=ovmfVersion,proto3" json:"ovmf_version,omitempty"`
|
||||
@@ -260,20 +277,20 @@ type SVMInfoRes struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) Reset() {
|
||||
*x = SVMInfoRes{}
|
||||
func (x *CVMInfoRes) Reset() {
|
||||
*x = CVMInfoRes{}
|
||||
mi := &file_manager_manager_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) String() string {
|
||||
func (x *CVMInfoRes) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SVMInfoRes) ProtoMessage() {}
|
||||
func (*CVMInfoRes) ProtoMessage() {}
|
||||
|
||||
func (x *SVMInfoRes) ProtoReflect() protoreflect.Message {
|
||||
func (x *CVMInfoRes) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_manager_manager_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -285,47 +302,47 @@ func (x *SVMInfoRes) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SVMInfoRes.ProtoReflect.Descriptor instead.
|
||||
func (*SVMInfoRes) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use CVMInfoRes.ProtoReflect.Descriptor instead.
|
||||
func (*CVMInfoRes) Descriptor() ([]byte, []int) {
|
||||
return file_manager_manager_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) GetId() string {
|
||||
func (x *CVMInfoRes) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) GetOvmfVersion() string {
|
||||
func (x *CVMInfoRes) GetOvmfVersion() string {
|
||||
if x != nil {
|
||||
return x.OvmfVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) GetCpuNum() int32 {
|
||||
func (x *CVMInfoRes) GetCpuNum() int32 {
|
||||
if x != nil {
|
||||
return x.CpuNum
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) GetCpuType() string {
|
||||
func (x *CVMInfoRes) GetCpuType() string {
|
||||
if x != nil {
|
||||
return x.CpuType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) GetKernelCmd() string {
|
||||
func (x *CVMInfoRes) GetKernelCmd() string {
|
||||
if x != nil {
|
||||
return x.KernelCmd
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SVMInfoRes) GetEosVersion() string {
|
||||
func (x *CVMInfoRes) GetEosVersion() string {
|
||||
if x != nil {
|
||||
return x.EosVersion
|
||||
}
|
||||
@@ -376,27 +393,27 @@ func (x *AttestationPolicyReq) GetId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type SVMInfoReq struct {
|
||||
type CVMInfoReq struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SVMInfoReq) Reset() {
|
||||
*x = SVMInfoReq{}
|
||||
func (x *CVMInfoReq) Reset() {
|
||||
*x = CVMInfoReq{}
|
||||
mi := &file_manager_manager_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SVMInfoReq) String() string {
|
||||
func (x *CVMInfoReq) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SVMInfoReq) ProtoMessage() {}
|
||||
func (*CVMInfoReq) ProtoMessage() {}
|
||||
|
||||
func (x *SVMInfoReq) ProtoReflect() protoreflect.Message {
|
||||
func (x *CVMInfoReq) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_manager_manager_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -408,12 +425,12 @@ func (x *SVMInfoReq) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SVMInfoReq.ProtoReflect.Descriptor instead.
|
||||
func (*SVMInfoReq) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use CVMInfoReq.ProtoReflect.Descriptor instead.
|
||||
func (*CVMInfoReq) Descriptor() ([]byte, []int) {
|
||||
return file_manager_manager_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *SVMInfoReq) GetId() string {
|
||||
func (x *CVMInfoReq) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
@@ -422,11 +439,11 @@ func (x *SVMInfoReq) GetId() string {
|
||||
|
||||
var File_manager_manager_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_manager_manager_proto_rawDesc = []byte{
|
||||
var file_manager_manager_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x15, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
|
||||
0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x80, 0x02,
|
||||
0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x02,
|
||||
0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x26, 0x0a, 0x0f, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65,
|
||||
@@ -443,61 +460,65 @@ var file_manager_manager_proto_rawDesc = []byte{
|
||||
0x2f, 0x0a, 0x14, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x76, 0x6d, 0x5f, 0x73, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x43, 0x76, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c,
|
||||
0x22, 0x49, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x12, 0x25, 0x0a,
|
||||
0x0e, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64,
|
||||
0x50, 0x6f, 0x72, 0x74, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x76, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x76, 0x6d, 0x49, 0x64, 0x22, 0x22, 0x0a, 0x09, 0x52,
|
||||
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x76, 0x6d, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x76, 0x6d, 0x49, 0x64, 0x22,
|
||||
0x3a, 0x0a, 0x14, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f,
|
||||
0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x0a,
|
||||
0x53, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x76,
|
||||
0x6d, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0b, 0x6f, 0x76, 0x6d, 0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a,
|
||||
0x07, 0x63, 0x70, 0x75, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06,
|
||||
0x63, 0x70, 0x75, 0x4e, 0x75, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x79,
|
||||
0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x70, 0x75, 0x54, 0x79, 0x70,
|
||||
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6d, 0x64, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x43, 0x6d, 0x64,
|
||||
0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x22, 0x26, 0x0a, 0x14, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x0a, 0x53, 0x56, 0x4d,
|
||||
0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x8c, 0x02, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x56, 0x6d, 0x12, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
|
||||
0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x6e,
|
||||
0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00,
|
||||
0x12, 0x38, 0x0a, 0x08, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x56, 0x6d, 0x12, 0x12, 0x2e, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71,
|
||||
0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, 0x53, 0x56,
|
||||
0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
|
||||
0x53, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6d, 0x61, 0x6e,
|
||||
0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x22,
|
||||
0x00, 0x12, 0x53, 0x0a, 0x11, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
|
||||
0x2e, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69,
|
||||
0x63, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
|
||||
0x12, 0x27, 0x0a, 0x10, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x76, 0x6d, 0x5f, 0x63, 0x61,
|
||||
0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x43, 0x76, 0x6d, 0x43, 0x61, 0x55, 0x72, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x74, 0x6c,
|
||||
0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, 0x49, 0x0a, 0x09, 0x43,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x77,
|
||||
0x61, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0d, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12,
|
||||
0x15, 0x0a, 0x06, 0x63, 0x76, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x63, 0x76, 0x6d, 0x49, 0x64, 0x22, 0x22, 0x0a, 0x09, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||
0x52, 0x65, 0x71, 0x12, 0x15, 0x0a, 0x06, 0x63, 0x76, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x76, 0x6d, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x14, 0x41, 0x74,
|
||||
0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52,
|
||||
0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0xb3, 0x01, 0x0a, 0x0a, 0x43, 0x56, 0x4d, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x52, 0x65, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x76, 0x6d, 0x66, 0x5f, 0x76, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x76, 0x6d,
|
||||
0x66, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x70, 0x75, 0x5f,
|
||||
0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x63, 0x70, 0x75, 0x4e, 0x75,
|
||||
0x6d, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x70, 0x75, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6d, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x43, 0x6d, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x65,
|
||||
0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x0a, 0x65, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x14,
|
||||
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
|
||||
0x79, 0x52, 0x65, 0x73, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
0x79, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x02, 0x69, 0x64, 0x22, 0x1c, 0x0a, 0x0a, 0x43, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52,
|
||||
0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x64, 0x32, 0x8c, 0x02, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x53, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x08, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56,
|
||||
0x6d, 0x12, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61,
|
||||
0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
|
||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x08, 0x52,
|
||||
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x56, 0x6d, 0x12, 0x12, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x07, 0x43, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f,
|
||||
0x12, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x43, 0x56, 0x4d, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e,
|
||||
0x43, 0x56, 0x4d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x11,
|
||||
0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63,
|
||||
0x79, 0x12, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x74, 0x74, 0x65,
|
||||
0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71,
|
||||
0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x41, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x73, 0x22,
|
||||
0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
file_manager_manager_proto_rawDescOnce sync.Once
|
||||
file_manager_manager_proto_rawDescData = file_manager_manager_proto_rawDesc
|
||||
file_manager_manager_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_manager_manager_proto_rawDescGZIP() []byte {
|
||||
file_manager_manager_proto_rawDescOnce.Do(func() {
|
||||
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(file_manager_manager_proto_rawDescData)
|
||||
file_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)))
|
||||
})
|
||||
return file_manager_manager_proto_rawDescData
|
||||
}
|
||||
@@ -508,19 +529,19 @@ var file_manager_manager_proto_goTypes = []any{
|
||||
(*CreateRes)(nil), // 1: manager.CreateRes
|
||||
(*RemoveReq)(nil), // 2: manager.RemoveReq
|
||||
(*AttestationPolicyRes)(nil), // 3: manager.AttestationPolicyRes
|
||||
(*SVMInfoRes)(nil), // 4: manager.SVMInfoRes
|
||||
(*CVMInfoRes)(nil), // 4: manager.CVMInfoRes
|
||||
(*AttestationPolicyReq)(nil), // 5: manager.AttestationPolicyReq
|
||||
(*SVMInfoReq)(nil), // 6: manager.SVMInfoReq
|
||||
(*CVMInfoReq)(nil), // 6: manager.CVMInfoReq
|
||||
(*emptypb.Empty)(nil), // 7: google.protobuf.Empty
|
||||
}
|
||||
var file_manager_manager_proto_depIdxs = []int32{
|
||||
0, // 0: manager.ManagerService.CreateVm:input_type -> manager.CreateReq
|
||||
2, // 1: manager.ManagerService.RemoveVm:input_type -> manager.RemoveReq
|
||||
6, // 2: manager.ManagerService.SVMInfo:input_type -> manager.SVMInfoReq
|
||||
6, // 2: manager.ManagerService.CVMInfo:input_type -> manager.CVMInfoReq
|
||||
5, // 3: manager.ManagerService.AttestationPolicy:input_type -> manager.AttestationPolicyReq
|
||||
1, // 4: manager.ManagerService.CreateVm:output_type -> manager.CreateRes
|
||||
7, // 5: manager.ManagerService.RemoveVm:output_type -> google.protobuf.Empty
|
||||
4, // 6: manager.ManagerService.SVMInfo:output_type -> manager.SVMInfoRes
|
||||
4, // 6: manager.ManagerService.CVMInfo:output_type -> manager.CVMInfoRes
|
||||
3, // 7: manager.ManagerService.AttestationPolicy:output_type -> manager.AttestationPolicyRes
|
||||
4, // [4:8] is the sub-list for method output_type
|
||||
0, // [0:4] is the sub-list for method input_type
|
||||
@@ -538,7 +559,7 @@ func file_manager_manager_proto_init() {
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_manager_manager_proto_rawDesc,
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
@@ -549,7 +570,6 @@ func file_manager_manager_proto_init() {
|
||||
MessageInfos: file_manager_manager_proto_msgTypes,
|
||||
}.Build()
|
||||
File_manager_manager_proto = out.File
|
||||
file_manager_manager_proto_rawDesc = nil
|
||||
file_manager_manager_proto_goTypes = nil
|
||||
file_manager_manager_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ option go_package = "./manager";
|
||||
service ManagerService {
|
||||
rpc CreateVm(CreateReq) returns (CreateRes) {}
|
||||
rpc RemoveVm(RemoveReq) returns (google.protobuf.Empty) {}
|
||||
rpc SVMInfo(SVMInfoReq) returns (SVMInfoRes) {}
|
||||
rpc CVMInfo(CVMInfoReq) returns (CVMInfoRes) {}
|
||||
rpc AttestationPolicy(AttestationPolicyReq) returns (AttestationPolicyRes) {}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,17 @@ message CreateReq{
|
||||
bytes agent_cvm_client_key = 3;
|
||||
bytes agent_cvm_client_cert = 4;
|
||||
string agent_cvm_server_url = 5;
|
||||
string agent_cvm_ca_url = 6;
|
||||
string ttl = 7;
|
||||
}
|
||||
|
||||
message CreateRes{
|
||||
string forwarded_port = 1;
|
||||
string svm_id = 2;
|
||||
string cvm_id = 2;
|
||||
}
|
||||
|
||||
message RemoveReq{
|
||||
string svm_id = 1;
|
||||
string cvm_id = 1;
|
||||
}
|
||||
|
||||
message AttestationPolicyRes{
|
||||
@@ -38,7 +40,7 @@ message AttestationPolicyRes{
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message SVMInfoRes{
|
||||
message CVMInfoRes{
|
||||
string id = 1;
|
||||
string ovmf_version = 2;
|
||||
int32 cpu_num = 3;
|
||||
@@ -51,7 +53,7 @@ message AttestationPolicyReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
message SVMInfoReq {
|
||||
message CVMInfoReq {
|
||||
string id = 1;
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -25,7 +25,7 @@ const _ = grpc.SupportPackageIsVersion9
|
||||
const (
|
||||
ManagerService_CreateVm_FullMethodName = "/manager.ManagerService/CreateVm"
|
||||
ManagerService_RemoveVm_FullMethodName = "/manager.ManagerService/RemoveVm"
|
||||
ManagerService_SVMInfo_FullMethodName = "/manager.ManagerService/SVMInfo"
|
||||
ManagerService_CVMInfo_FullMethodName = "/manager.ManagerService/CVMInfo"
|
||||
ManagerService_AttestationPolicy_FullMethodName = "/manager.ManagerService/AttestationPolicy"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ const (
|
||||
type ManagerServiceClient interface {
|
||||
CreateVm(ctx context.Context, in *CreateReq, opts ...grpc.CallOption) (*CreateRes, error)
|
||||
RemoveVm(ctx context.Context, in *RemoveReq, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SVMInfo(ctx context.Context, in *SVMInfoReq, opts ...grpc.CallOption) (*SVMInfoRes, error)
|
||||
CVMInfo(ctx context.Context, in *CVMInfoReq, opts ...grpc.CallOption) (*CVMInfoRes, error)
|
||||
AttestationPolicy(ctx context.Context, in *AttestationPolicyReq, opts ...grpc.CallOption) (*AttestationPolicyRes, error)
|
||||
}
|
||||
|
||||
@@ -67,10 +67,10 @@ func (c *managerServiceClient) RemoveVm(ctx context.Context, in *RemoveReq, opts
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *managerServiceClient) SVMInfo(ctx context.Context, in *SVMInfoReq, opts ...grpc.CallOption) (*SVMInfoRes, error) {
|
||||
func (c *managerServiceClient) CVMInfo(ctx context.Context, in *CVMInfoReq, opts ...grpc.CallOption) (*CVMInfoRes, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SVMInfoRes)
|
||||
err := c.cc.Invoke(ctx, ManagerService_SVMInfo_FullMethodName, in, out, cOpts...)
|
||||
out := new(CVMInfoRes)
|
||||
err := c.cc.Invoke(ctx, ManagerService_CVMInfo_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (c *managerServiceClient) AttestationPolicy(ctx context.Context, in *Attest
|
||||
type ManagerServiceServer interface {
|
||||
CreateVm(context.Context, *CreateReq) (*CreateRes, error)
|
||||
RemoveVm(context.Context, *RemoveReq) (*emptypb.Empty, error)
|
||||
SVMInfo(context.Context, *SVMInfoReq) (*SVMInfoRes, error)
|
||||
CVMInfo(context.Context, *CVMInfoReq) (*CVMInfoRes, error)
|
||||
AttestationPolicy(context.Context, *AttestationPolicyReq) (*AttestationPolicyRes, error)
|
||||
mustEmbedUnimplementedManagerServiceServer()
|
||||
}
|
||||
@@ -111,8 +111,8 @@ func (UnimplementedManagerServiceServer) CreateVm(context.Context, *CreateReq) (
|
||||
func (UnimplementedManagerServiceServer) RemoveVm(context.Context, *RemoveReq) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RemoveVm not implemented")
|
||||
}
|
||||
func (UnimplementedManagerServiceServer) SVMInfo(context.Context, *SVMInfoReq) (*SVMInfoRes, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SVMInfo not implemented")
|
||||
func (UnimplementedManagerServiceServer) CVMInfo(context.Context, *CVMInfoReq) (*CVMInfoRes, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method CVMInfo not implemented")
|
||||
}
|
||||
func (UnimplementedManagerServiceServer) AttestationPolicy(context.Context, *AttestationPolicyReq) (*AttestationPolicyRes, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AttestationPolicy not implemented")
|
||||
@@ -174,20 +174,20 @@ func _ManagerService_RemoveVm_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ManagerService_SVMInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SVMInfoReq)
|
||||
func _ManagerService_CVMInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(CVMInfoReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ManagerServiceServer).SVMInfo(ctx, in)
|
||||
return srv.(ManagerServiceServer).CVMInfo(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ManagerService_SVMInfo_FullMethodName,
|
||||
FullMethod: ManagerService_CVMInfo_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ManagerServiceServer).SVMInfo(ctx, req.(*SVMInfoReq))
|
||||
return srv.(ManagerServiceServer).CVMInfo(ctx, req.(*CVMInfoReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@@ -226,8 +226,8 @@ var ManagerService_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _ManagerService_RemoveVm_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SVMInfo",
|
||||
Handler: _ManagerService_SVMInfo_Handler,
|
||||
MethodName: "CVMInfo",
|
||||
Handler: _ManagerService_CVMInfo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AttestationPolicy",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -103,6 +103,80 @@ func (_c *ManagerServiceClient_AttestationPolicy_Call) RunAndReturn(run func(con
|
||||
return _c
|
||||
}
|
||||
|
||||
// CVMInfo provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *ManagerServiceClient) CVMInfo(ctx context.Context, in *manager.CVMInfoReq, opts ...grpc.CallOption) (*manager.CVMInfoRes, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CVMInfo")
|
||||
}
|
||||
|
||||
var r0 *manager.CVMInfoRes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) (*manager.CVMInfoRes, error)); ok {
|
||||
return rf(ctx, in, opts...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) *manager.CVMInfoRes); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*manager.CVMInfoRes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ManagerServiceClient_CVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CVMInfo'
|
||||
type ManagerServiceClient_CVMInfo_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CVMInfo is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - in *manager.CVMInfoReq
|
||||
// - opts ...grpc.CallOption
|
||||
func (_e *ManagerServiceClient_Expecter) CVMInfo(ctx interface{}, in interface{}, opts ...interface{}) *ManagerServiceClient_CVMInfo_Call {
|
||||
return &ManagerServiceClient_CVMInfo_Call{Call: _e.mock.On("CVMInfo",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
}
|
||||
|
||||
func (_c *ManagerServiceClient_CVMInfo_Call) Run(run func(ctx context.Context, in *manager.CVMInfoReq, opts ...grpc.CallOption)) *ManagerServiceClient_CVMInfo_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]grpc.CallOption, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(grpc.CallOption)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*manager.CVMInfoReq), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ManagerServiceClient_CVMInfo_Call) Return(_a0 *manager.CVMInfoRes, _a1 error) *ManagerServiceClient_CVMInfo_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ManagerServiceClient_CVMInfo_Call) RunAndReturn(run func(context.Context, *manager.CVMInfoReq, ...grpc.CallOption) (*manager.CVMInfoRes, error)) *ManagerServiceClient_CVMInfo_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreateVm provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *ManagerServiceClient) CreateVm(ctx context.Context, in *manager.CreateReq, opts ...grpc.CallOption) (*manager.CreateRes, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
@@ -251,80 +325,6 @@ func (_c *ManagerServiceClient_RemoveVm_Call) RunAndReturn(run func(context.Cont
|
||||
return _c
|
||||
}
|
||||
|
||||
// SVMInfo provides a mock function with given fields: ctx, in, opts
|
||||
func (_m *ManagerServiceClient) SVMInfo(ctx context.Context, in *manager.SVMInfoReq, opts ...grpc.CallOption) (*manager.SVMInfoRes, error) {
|
||||
_va := make([]interface{}, len(opts))
|
||||
for _i := range opts {
|
||||
_va[_i] = opts[_i]
|
||||
}
|
||||
var _ca []interface{}
|
||||
_ca = append(_ca, ctx, in)
|
||||
_ca = append(_ca, _va...)
|
||||
ret := _m.Called(_ca...)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SVMInfo")
|
||||
}
|
||||
|
||||
var r0 *manager.SVMInfoRes
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) (*manager.SVMInfoRes, error)); ok {
|
||||
return rf(ctx, in, opts...)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) *manager.SVMInfoRes); ok {
|
||||
r0 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*manager.SVMInfoRes)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) error); ok {
|
||||
r1 = rf(ctx, in, opts...)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ManagerServiceClient_SVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SVMInfo'
|
||||
type ManagerServiceClient_SVMInfo_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SVMInfo is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - in *manager.SVMInfoReq
|
||||
// - opts ...grpc.CallOption
|
||||
func (_e *ManagerServiceClient_Expecter) SVMInfo(ctx interface{}, in interface{}, opts ...interface{}) *ManagerServiceClient_SVMInfo_Call {
|
||||
return &ManagerServiceClient_SVMInfo_Call{Call: _e.mock.On("SVMInfo",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
}
|
||||
|
||||
func (_c *ManagerServiceClient_SVMInfo_Call) Run(run func(ctx context.Context, in *manager.SVMInfoReq, opts ...grpc.CallOption)) *ManagerServiceClient_SVMInfo_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := make([]grpc.CallOption, len(args)-2)
|
||||
for i, a := range args[2:] {
|
||||
if a != nil {
|
||||
variadicArgs[i] = a.(grpc.CallOption)
|
||||
}
|
||||
}
|
||||
run(args[0].(context.Context), args[1].(*manager.SVMInfoReq), variadicArgs...)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ManagerServiceClient_SVMInfo_Call) Return(_a0 *manager.SVMInfoRes, _a1 error) *ManagerServiceClient_SVMInfo_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *ManagerServiceClient_SVMInfo_Call) RunAndReturn(run func(context.Context, *manager.SVMInfoReq, ...grpc.CallOption) (*manager.SVMInfoRes, error)) *ManagerServiceClient_SVMInfo_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewManagerServiceClient creates a new instance of ManagerServiceClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewManagerServiceClient(t interface {
|
||||
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -195,12 +195,12 @@ func (_c *Service_RemoveVM_Call) RunAndReturn(run func(context.Context, string)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ReturnSVMInfo provides a mock function with given fields: ctx
|
||||
func (_m *Service) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
// ReturnCVMInfo provides a mock function with given fields: ctx
|
||||
func (_m *Service) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ReturnSVMInfo")
|
||||
panic("no return value specified for ReturnCVMInfo")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
@@ -237,30 +237,30 @@ func (_m *Service) ReturnSVMInfo(ctx context.Context) (string, int, string, stri
|
||||
return r0, r1, r2, r3
|
||||
}
|
||||
|
||||
// Service_ReturnSVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReturnSVMInfo'
|
||||
type Service_ReturnSVMInfo_Call struct {
|
||||
// Service_ReturnCVMInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReturnCVMInfo'
|
||||
type Service_ReturnCVMInfo_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ReturnSVMInfo is a helper method to define mock.On call
|
||||
// ReturnCVMInfo is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *Service_Expecter) ReturnSVMInfo(ctx interface{}) *Service_ReturnSVMInfo_Call {
|
||||
return &Service_ReturnSVMInfo_Call{Call: _e.mock.On("ReturnSVMInfo", ctx)}
|
||||
func (_e *Service_Expecter) ReturnCVMInfo(ctx interface{}) *Service_ReturnCVMInfo_Call {
|
||||
return &Service_ReturnCVMInfo_Call{Call: _e.mock.On("ReturnCVMInfo", ctx)}
|
||||
}
|
||||
|
||||
func (_c *Service_ReturnSVMInfo_Call) Run(run func(ctx context.Context)) *Service_ReturnSVMInfo_Call {
|
||||
func (_c *Service_ReturnCVMInfo_Call) Run(run func(ctx context.Context)) *Service_ReturnCVMInfo_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ReturnSVMInfo_Call) Return(_a0 string, _a1 int, _a2 string, _a3 string) *Service_ReturnSVMInfo_Call {
|
||||
func (_c *Service_ReturnCVMInfo_Call) Return(_a0 string, _a1 int, _a2 string, _a3 string) *Service_ReturnCVMInfo_Call {
|
||||
_c.Call.Return(_a0, _a1, _a2, _a3)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ReturnSVMInfo_Call) RunAndReturn(run func(context.Context) (string, int, string, string)) *Service_ReturnSVMInfo_Call {
|
||||
func (_c *Service_ReturnCVMInfo_Call) RunAndReturn(run func(context.Context) (string, int, string, string)) *Service_ReturnCVMInfo_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
+20
-15
@@ -56,7 +56,13 @@ type SevConfig struct {
|
||||
ID string `env:"SEV_ID" envDefault:"sev0"`
|
||||
CBitPos int `env:"SEV_CBITPOS" envDefault:"51"`
|
||||
ReducedPhysBits int `env:"SEV_REDUCED_PHYS_BITS" envDefault:"1"`
|
||||
HostData string `env:"HOST_DATA" envDefault:""`
|
||||
EnableHostData bool `env:"ENABLE_HOST_DATA" envDefault:"false"`
|
||||
HostData string `env:"HOST_DATA" envDefault:""`
|
||||
}
|
||||
|
||||
type IGVMConfig struct {
|
||||
ID string `env:"IGVM_ID" envDefault:"igvm0"`
|
||||
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
|
||||
}
|
||||
|
||||
type VSockConfig struct {
|
||||
@@ -80,9 +86,6 @@ type Config struct {
|
||||
MemID string `env:"MEM_ID" envDefault:"ram1"`
|
||||
MemoryConfig
|
||||
|
||||
// Kernel hash
|
||||
KernelHash bool `env:"KERNEL_HASH" envDefault:"false"`
|
||||
|
||||
// OVMF
|
||||
OVMFCodeConfig
|
||||
OVMFVarsConfig
|
||||
@@ -100,6 +103,9 @@ type Config struct {
|
||||
// SEV
|
||||
SevConfig
|
||||
|
||||
// vTPM
|
||||
IGVMConfig
|
||||
|
||||
// display
|
||||
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
|
||||
Monitor string `env:"MONITOR" envDefault:"pty"`
|
||||
@@ -173,40 +179,39 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
// SEV
|
||||
if config.EnableSEV || config.EnableSEVSNP {
|
||||
sevType := "sev-guest"
|
||||
kernelHash := ""
|
||||
hostData := ""
|
||||
|
||||
args = append(args, "-machine",
|
||||
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s",
|
||||
fmt.Sprintf("confidential-guest-support=%s,memory-backend=%s,igvm-cfg=%s",
|
||||
config.SevConfig.ID,
|
||||
config.MemID))
|
||||
config.MemID,
|
||||
config.IGVMConfig.ID))
|
||||
|
||||
if config.EnableSEVSNP {
|
||||
args = append(args, "-bios", config.OVMFCodeConfig.File)
|
||||
sevType = "sev-snp-guest"
|
||||
|
||||
if config.SevConfig.HostData != "" {
|
||||
if config.SevConfig.EnableHostData {
|
||||
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
|
||||
}
|
||||
}
|
||||
|
||||
if config.KernelHash {
|
||||
kernelHash = ",kernel-hashes=on"
|
||||
}
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("memory-backend-memfd,id=%s,size=%s,share=true,prealloc=false",
|
||||
config.MemID,
|
||||
config.MemoryConfig.Size))
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s%s",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
|
||||
sevType,
|
||||
config.SevConfig.ID,
|
||||
config.SevConfig.CBitPos,
|
||||
config.SevConfig.ReducedPhysBits,
|
||||
kernelHash,
|
||||
hostData))
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("igvm-cfg,id=%s,file=%s",
|
||||
config.IGVMConfig.ID,
|
||||
config.IGVMConfig.File))
|
||||
}
|
||||
|
||||
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
|
||||
|
||||
@@ -132,6 +132,10 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
},
|
||||
IGVMConfig: IGVMConfig{
|
||||
ID: "igvm0",
|
||||
File: "/test/path/cocos-igvm.igvm",
|
||||
},
|
||||
NoGraphic: true,
|
||||
Monitor: "pty",
|
||||
},
|
||||
@@ -144,10 +148,10 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
|
||||
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
|
||||
"-device", "vhost-vsock-pci,id=vhost-vsock-pci0,guest-cid=3",
|
||||
"-machine", "confidential-guest-support=sev0,memory-backend=ram1",
|
||||
"-bios", "/usr/share/OVMF/OVMF_CODE.fd",
|
||||
"-machine", "confidential-guest-support=sev0,memory-backend=ram1,igvm-cfg=igvm0",
|
||||
"-object", "memory-backend-memfd,id=ram1,size=2048M,share=true,prealloc=false",
|
||||
"-object", "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1",
|
||||
"-object", "igvm-cfg,id=igvm0,file=/test/path/cocos-igvm.igvm",
|
||||
"-kernel", "img/bzImage",
|
||||
"-append", "\"quiet console=null\"",
|
||||
"-initrd", "img/rootfs.cpio.gz",
|
||||
@@ -167,37 +171,6 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_KernelHash(t *testing.T) {
|
||||
config := Config{
|
||||
EnableSEVSNP: true,
|
||||
KernelHash: true,
|
||||
SevConfig: SevConfig{
|
||||
ID: "sev0",
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
},
|
||||
}
|
||||
|
||||
result := config.ConstructQemuArgs()
|
||||
|
||||
expected := "-object"
|
||||
expectedValue := "sev-snp-guest,id=sev0,cbitpos=51,reduced-phys-bits=1,kernel-hashes=on"
|
||||
|
||||
found := false
|
||||
for i, arg := range result {
|
||||
if arg == expected && i+1 < len(result) {
|
||||
if result[i+1] == expectedValue {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("ConstructQemuArgs() did not contain expected SEV-SNP configuration with kernel hashes enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_HostData(t *testing.T) {
|
||||
config := Config{
|
||||
EnableSEVSNP: true,
|
||||
@@ -205,6 +178,7 @@ func TestConstructQemuArgs_HostData(t *testing.T) {
|
||||
ID: "sev0",
|
||||
CBitPos: 51,
|
||||
ReducedPhysBits: 1,
|
||||
EnableHostData: true,
|
||||
HostData: "test-host-data",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -69,7 +69,7 @@ func (_c *Persistence_DeleteVM_Call) RunAndReturn(run func(string) error) *Persi
|
||||
return _c
|
||||
}
|
||||
|
||||
// LoadVMs provides a mock function with given fields:
|
||||
// LoadVMs provides a mock function with no fields
|
||||
func (_m *Persistence) LoadVMs() ([]qemu.VMState, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
|
||||
+13
-10
@@ -4,6 +4,7 @@ package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
@@ -30,17 +31,19 @@ type VMInfo struct {
|
||||
}
|
||||
|
||||
type qemuVM struct {
|
||||
vmi VMInfo
|
||||
cmd *exec.Cmd
|
||||
computationId string
|
||||
vmi VMInfo
|
||||
cmd *exec.Cmd
|
||||
cvmId string
|
||||
logger *slog.Logger
|
||||
vm.StateMachine
|
||||
}
|
||||
|
||||
func NewVM(config interface{}, computationId string) vm.VM {
|
||||
func NewVM(config interface{}, cvmId string, logger *slog.Logger) vm.VM {
|
||||
return &qemuVM{
|
||||
vmi: config.(VMInfo),
|
||||
computationId: computationId,
|
||||
StateMachine: vm.NewStateMachine(),
|
||||
vmi: config.(VMInfo),
|
||||
cvmId: cvmId,
|
||||
StateMachine: vm.NewStateMachine(),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +62,7 @@ func (v *qemuVM) Start() (err error) {
|
||||
v.vmi.Config.NetDevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.NetDevConfig.ID, id)
|
||||
v.vmi.Config.SevConfig.ID = fmt.Sprintf("%s-%s", v.vmi.Config.SevConfig.ID, id)
|
||||
|
||||
if !v.vmi.Config.KernelHash {
|
||||
if !v.vmi.Config.EnableSEVSNP {
|
||||
// Copy firmware vars file.
|
||||
srcFile := v.vmi.Config.OVMFVarsConfig.File
|
||||
dstFile := fmt.Sprintf("%s/%s-%s.fd", tmpDir, firmwareVars, id)
|
||||
@@ -76,8 +79,8 @@ func (v *qemuVM) Start() (err error) {
|
||||
}
|
||||
|
||||
v.cmd = exec.Command(exe, args...)
|
||||
v.cmd.Stdout = os.Stdout
|
||||
v.cmd.Stderr = os.Stderr
|
||||
v.cmd.Stdout = &vm.Stdout{StateMachine: v.StateMachine, Logger: v.logger.With(slog.String("cvm", v.cvmId))}
|
||||
v.cmd.Stderr = &vm.Stderr{StateMachine: v.StateMachine, Logger: v.logger.With(slog.String("cvm", v.cvmId))}
|
||||
|
||||
return v.cmd.Start()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
@@ -17,7 +18,7 @@ const testComputationID = "test-computation"
|
||||
func TestNewVM(t *testing.T) {
|
||||
config := VMInfo{Config: Config{}}
|
||||
|
||||
vm := NewVM(config, testComputationID)
|
||||
vm := NewVM(config, testComputationID, slog.Default())
|
||||
|
||||
assert.NotNil(t, vm)
|
||||
assert.IsType(t, &qemuVM{}, vm)
|
||||
@@ -36,7 +37,7 @@ func TestStart(t *testing.T) {
|
||||
QemuBinPath: "echo",
|
||||
}}
|
||||
|
||||
vm := NewVM(config, testComputationID).(*qemuVM)
|
||||
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
|
||||
|
||||
err = vm.Start()
|
||||
assert.NoError(t, err)
|
||||
@@ -59,7 +60,7 @@ func TestStartSudo(t *testing.T) {
|
||||
UseSudo: true,
|
||||
}}
|
||||
|
||||
vm := NewVM(config, testComputationID).(*qemuVM)
|
||||
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
|
||||
|
||||
err = vm.Start()
|
||||
assert.NoError(t, err)
|
||||
|
||||
+55
-20
@@ -3,26 +3,29 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/uuid"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -33,6 +36,8 @@ const (
|
||||
agentCvmClientCertKey = "AGENT_CVM_GRPC_CLIENT_CERT"
|
||||
agentCvmClientKey = "AGENT_CVM_GRPC_CLIENT_KEY"
|
||||
agentCvmServerCaCertKey = "AGENT_CVM_GRPC_SERVER_CA_CERTS"
|
||||
agentCvmId = "AGENT_CVM_ID"
|
||||
agentCvmCaUrl = "AGENT_CVM_CA_URL"
|
||||
defClientCertPath = "/etc/certs/cert.pem"
|
||||
defClientKeyPath = "/etc/certs/key.pem"
|
||||
defServerCaCertPath = "/etc/certs/ca.pem"
|
||||
@@ -76,8 +81,8 @@ type Service interface {
|
||||
RemoveVM(ctx context.Context, computationID string) error
|
||||
// FetchAttestationPolicy measures and fetches the attestation policy.
|
||||
FetchAttestationPolicy(ctx context.Context, computationID string) ([]byte, error)
|
||||
// ReturnSVMInfo returns SVM information needed for attestation verification and validation.
|
||||
ReturnSVMInfo(ctx context.Context) (string, int, string, string)
|
||||
// ReturnCVMInfo returns CVM information needed for attestation verification and validation.
|
||||
ReturnCVMInfo(ctx context.Context) (string, int, string, string)
|
||||
}
|
||||
|
||||
type managerService struct {
|
||||
@@ -85,6 +90,8 @@ type managerService struct {
|
||||
ap sync.Mutex
|
||||
qemuCfg qemu.Config
|
||||
attestationPolicyBinaryPath string
|
||||
igvmMeasurementBinaryPath string
|
||||
pcrValuesFilePath string
|
||||
logger *slog.Logger
|
||||
vms map[string]vm.VM
|
||||
vmFactory vm.Provider
|
||||
@@ -97,7 +104,7 @@ type managerService struct {
|
||||
var _ Service = (*managerService)(nil)
|
||||
|
||||
// New instantiates the manager service implementation.
|
||||
func New(cfg qemu.Config, attestationPolicyBinPath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
|
||||
func New(cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
|
||||
start, end, err := decodeRange(cfg.HostFwdRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -114,6 +121,8 @@ func New(cfg qemu.Config, attestationPolicyBinPath string, logger *slog.Logger,
|
||||
vms: make(map[string]vm.VM),
|
||||
vmFactory: vmFactory,
|
||||
attestationPolicyBinaryPath: attestationPolicyBinPath,
|
||||
igvmMeasurementBinaryPath: igvmMeasurementBinaryPath,
|
||||
pcrValuesFilePath: pcrValuesFilePath,
|
||||
portRangeMin: start,
|
||||
portRangeMax: end,
|
||||
persistence: persistence,
|
||||
@@ -150,30 +159,36 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
cfg.Config.EnvMount = tmpEnvDir
|
||||
|
||||
if ms.qemuCfg.EnableSEVSNP || ms.qemuCfg.EnableSEV {
|
||||
cmd := exec.Command("sudo", fmt.Sprintf("%s/attestation_policy", ms.attestationPolicyBinaryPath), "--policy", "196608")
|
||||
var stderrBuffer bytes.Buffer
|
||||
options := []string{"--policy", "196608"}
|
||||
|
||||
if ms.pcrValuesFilePath != "" {
|
||||
pcrValues := []string{"--pcr", ms.pcrValuesFilePath}
|
||||
options = append(options, pcrValues...)
|
||||
}
|
||||
|
||||
stderr := bufio.NewWriter(&stderrBuffer)
|
||||
|
||||
attestPolicyCmd, err := cmdconfig.NewCmdConfig("sudo", options, stderr)
|
||||
if err != nil {
|
||||
return "", id, err
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
_, err := cmd.Output()
|
||||
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return "", id, errors.Wrap(ErrFailedToCreateAttestationPolicy, err)
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
f, err := os.ReadFile("./attestation_policy.json")
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return "", id, errors.Wrap(ErrFailedToReadPolicy, err)
|
||||
}
|
||||
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
var attestationPolicy check.Config
|
||||
|
||||
if err = protojson.Unmarshal(f, &attestationPolicy); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
return "", id, errors.Wrap(ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
// Define the TCB that was present at launch of the VM.
|
||||
cfg.LaunchTCB = attestationPolicy.Policy.MinimumLaunchTcb
|
||||
cfg.LaunchTCB = attestationPolicy.Config.Policy.MinimumLaunchTcb
|
||||
}
|
||||
|
||||
agentPort, err := getFreePort(ms.portRangeMin, ms.portRangeMax)
|
||||
@@ -204,7 +219,7 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
cfg.Config.SevConfig.HostData = base64.StdEncoding.EncodeToString(todo[:])
|
||||
}
|
||||
|
||||
cvm := ms.vmFactory(cfg, id)
|
||||
cvm := ms.vmFactory(cfg, id, ms.logger)
|
||||
if err = cvm.Start(); err != nil {
|
||||
return "", id, err
|
||||
}
|
||||
@@ -212,6 +227,24 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
ms.vms[id] = cvm
|
||||
ms.mu.Unlock()
|
||||
|
||||
if req.Ttl != "" {
|
||||
ttl, err := time.ParseDuration(req.Ttl)
|
||||
if err != nil {
|
||||
return "", id, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-time.After(ttl):
|
||||
if err := ms.RemoveVM(ctx, id); err != nil {
|
||||
ms.logger.Error("Failed to remove VM after TTL", "error", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
pid := cvm.GetProcess()
|
||||
|
||||
state := qemu.VMState{
|
||||
@@ -251,7 +284,7 @@ func (ms *managerService) RemoveVM(ctx context.Context, computationID string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *managerService) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
func (ms *managerService) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
return ms.qemuCfg.OVMFCodeConfig.Version, ms.qemuCfg.SMPCount, ms.qemuCfg.CPU, ms.eosVersion
|
||||
}
|
||||
|
||||
@@ -338,7 +371,7 @@ func (ms *managerService) restoreVMs() error {
|
||||
continue
|
||||
}
|
||||
|
||||
cvm := ms.vmFactory(state.VMinfo, state.ID)
|
||||
cvm := ms.vmFactory(state.VMinfo, state.ID, ms.logger)
|
||||
|
||||
if err = cvm.SetProcess(state.PID); err != nil {
|
||||
ms.logger.Warn("Failed to reattach to process", "computation", state.ID, "pid", state.PID, "error", err)
|
||||
@@ -402,6 +435,8 @@ func tmpEnvironment(id string, req *CreateReq) (string, error) {
|
||||
envMap := map[string]string{
|
||||
agentLogLevelKey: req.AgentLogLevel,
|
||||
agentCvmGrpcUrlKey: req.AgentCvmServerUrl,
|
||||
agentCvmId: id,
|
||||
agentCvmCaUrl: req.AgentCvmCaUrl,
|
||||
}
|
||||
|
||||
if req.AgentCvmClientCert != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
@@ -29,7 +30,7 @@ func TestNew(t *testing.T) {
|
||||
logger := slog.Default()
|
||||
vmf := new(mocks.Provider)
|
||||
|
||||
service, err := New(cfg, "", logger, vmf.Execute, "")
|
||||
service, err := New(cfg, "", "", "", logger, vmf.Execute, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, service)
|
||||
@@ -94,7 +95,8 @@ func TestRun(t *testing.T) {
|
||||
|
||||
ms := &managerService{
|
||||
qemuCfg: qemuCfg,
|
||||
attestationPolicyBinaryPath: tempDir,
|
||||
attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"),
|
||||
pcrValuesFilePath: tempDir,
|
||||
logger: logger,
|
||||
vms: make(map[string]vm.VM),
|
||||
vmFactory: vmf.Execute,
|
||||
|
||||
@@ -42,9 +42,9 @@ func (tm *tracingMiddleware) FetchAttestationPolicy(ctx context.Context, computa
|
||||
return tm.svc.FetchAttestationPolicy(ctx, computationId)
|
||||
}
|
||||
|
||||
func (tm *tracingMiddleware) ReturnSVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
_, span := tm.tracer.Start(ctx, "return_svm_info")
|
||||
func (tm *tracingMiddleware) ReturnCVMInfo(ctx context.Context) (string, int, string, string) {
|
||||
_, span := tm.tracer.Start(ctx, "return_cvm_info")
|
||||
defer span.End()
|
||||
|
||||
return tm.svc.ReturnSVMInfo(ctx)
|
||||
return tm.svc.ReturnCVMInfo(ctx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package vm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
_ io.Writer = &Stdout{}
|
||||
_ io.Writer = &Stderr{}
|
||||
)
|
||||
|
||||
const bufSize = 1024
|
||||
|
||||
type Stdout struct {
|
||||
StateMachine StateMachine
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (s *Stdout) Write(p []byte) (n int, err error) {
|
||||
inBuf := bytes.NewBuffer(p)
|
||||
|
||||
buf := make([]byte, bufSize)
|
||||
|
||||
for {
|
||||
n, err := inBuf.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return len(p) - inBuf.Len(), err
|
||||
}
|
||||
|
||||
args := []any{
|
||||
slog.String("state", s.StateMachine.State()),
|
||||
}
|
||||
|
||||
s.Logger.Info(string(buf[:n]), args...)
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
type Stderr struct {
|
||||
StateMachine StateMachine
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (s *Stderr) Write(p []byte) (n int, err error) {
|
||||
inBuf := bytes.NewBuffer(p)
|
||||
|
||||
buf := make([]byte, bufSize)
|
||||
|
||||
for {
|
||||
n, err := inBuf.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return len(p) - inBuf.Len(), err
|
||||
}
|
||||
|
||||
args := []any{
|
||||
slog.String("state", s.StateMachine.State()),
|
||||
}
|
||||
|
||||
if strings.Contains(string(buf[:n]), "Error") {
|
||||
s.Logger.Error(string(buf[:n]), args...)
|
||||
} else {
|
||||
s.Logger.Warn(string(buf[:n]), args...)
|
||||
}
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
slog "log/slog"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
vm "github.com/ultravioletrs/cocos/manager/vm"
|
||||
)
|
||||
|
||||
@@ -23,17 +26,17 @@ func (_m *Provider) EXPECT() *Provider_Expecter {
|
||||
return &Provider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields: config, computationId
|
||||
func (_m *Provider) Execute(config interface{}, computationId string) vm.VM {
|
||||
ret := _m.Called(config, computationId)
|
||||
// Execute provides a mock function with given fields: config, computationId, logger
|
||||
func (_m *Provider) Execute(config interface{}, computationId string, logger *slog.Logger) vm.VM {
|
||||
ret := _m.Called(config, computationId, logger)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Execute")
|
||||
}
|
||||
|
||||
var r0 vm.VM
|
||||
if rf, ok := ret.Get(0).(func(interface{}, string) vm.VM); ok {
|
||||
r0 = rf(config, computationId)
|
||||
if rf, ok := ret.Get(0).(func(interface{}, string, *slog.Logger) vm.VM); ok {
|
||||
r0 = rf(config, computationId, logger)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(vm.VM)
|
||||
@@ -51,13 +54,14 @@ type Provider_Execute_Call struct {
|
||||
// Execute is a helper method to define mock.On call
|
||||
// - config interface{}
|
||||
// - computationId string
|
||||
func (_e *Provider_Expecter) Execute(config interface{}, computationId interface{}) *Provider_Execute_Call {
|
||||
return &Provider_Execute_Call{Call: _e.mock.On("Execute", config, computationId)}
|
||||
// - logger *slog.Logger
|
||||
func (_e *Provider_Expecter) Execute(config interface{}, computationId interface{}, logger interface{}) *Provider_Execute_Call {
|
||||
return &Provider_Execute_Call{Call: _e.mock.On("Execute", config, computationId, logger)}
|
||||
}
|
||||
|
||||
func (_c *Provider_Execute_Call) Run(run func(config interface{}, computationId string)) *Provider_Execute_Call {
|
||||
func (_c *Provider_Execute_Call) Run(run func(config interface{}, computationId string, logger *slog.Logger)) *Provider_Execute_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(interface{}), args[1].(string))
|
||||
run(args[0].(interface{}), args[1].(string), args[2].(*slog.Logger))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -67,7 +71,7 @@ func (_c *Provider_Execute_Call) Return(_a0 vm.VM) *Provider_Execute_Call {
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Provider_Execute_Call) RunAndReturn(run func(interface{}, string) vm.VM) *Provider_Execute_Call {
|
||||
func (_c *Provider_Execute_Call) RunAndReturn(run func(interface{}, string, *slog.Logger) vm.VM) *Provider_Execute_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -23,7 +23,7 @@ func (_m *StateMachine) EXPECT() *StateMachine_Expecter {
|
||||
return &StateMachine_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// State provides a mock function with given fields:
|
||||
// State provides a mock function with no fields
|
||||
func (_m *StateMachine) State() string {
|
||||
ret := _m.Called()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.43.2. DO NOT EDIT.
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -25,7 +25,7 @@ func (_m *VM) EXPECT() *VM_Expecter {
|
||||
return &VM_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetCID provides a mock function with given fields:
|
||||
// GetCID provides a mock function with no fields
|
||||
func (_m *VM) GetCID() int {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -70,7 +70,7 @@ func (_c *VM_GetCID_Call) RunAndReturn(run func() int) *VM_GetCID_Call {
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetConfig provides a mock function with given fields:
|
||||
// GetConfig provides a mock function with no fields
|
||||
func (_m *VM) GetConfig() interface{} {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -117,7 +117,7 @@ func (_c *VM_GetConfig_Call) RunAndReturn(run func() interface{}) *VM_GetConfig_
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetProcess provides a mock function with given fields:
|
||||
// GetProcess provides a mock function with no fields
|
||||
func (_m *VM) GetProcess() int {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -254,7 +254,7 @@ func (_c *VM_SetProcess_Call) RunAndReturn(run func(int) error) *VM_SetProcess_C
|
||||
return _c
|
||||
}
|
||||
|
||||
// Start provides a mock function with given fields:
|
||||
// Start provides a mock function with no fields
|
||||
func (_m *VM) Start() error {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -299,7 +299,7 @@ func (_c *VM_Start_Call) RunAndReturn(run func() error) *VM_Start_Call {
|
||||
return _c
|
||||
}
|
||||
|
||||
// State provides a mock function with given fields:
|
||||
// State provides a mock function with no fields
|
||||
func (_m *VM) State() string {
|
||||
ret := _m.Called()
|
||||
|
||||
@@ -344,7 +344,7 @@ func (_c *VM_State_Call) RunAndReturn(run func() string) *VM_State_Call {
|
||||
return _c
|
||||
}
|
||||
|
||||
// Stop provides a mock function with given fields:
|
||||
// Stop provides a mock function with no fields
|
||||
func (_m *VM) Stop() error {
|
||||
ret := _m.Called()
|
||||
|
||||
|
||||
+3
-1
@@ -3,6 +3,8 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
pkgmanager "github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -21,7 +23,7 @@ type VM interface {
|
||||
GetConfig() interface{}
|
||||
}
|
||||
|
||||
type Provider func(config interface{}, computationId string) VM
|
||||
type Provider func(config interface{}, computationId string, logger *slog.Logger) VM
|
||||
|
||||
type Event struct {
|
||||
EventType string
|
||||
|
||||
+22
-1
@@ -100,8 +100,29 @@ packages:
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/agent/cvms/server:
|
||||
interfaces:
|
||||
AgentServerProvider:
|
||||
AgentServer:
|
||||
config:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "server.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/google/go-sev-guest/client:
|
||||
interfaces:
|
||||
LeveledQuoteProvider:
|
||||
config:
|
||||
dir: "./pkg/attestation/quoteprovider/mocks"
|
||||
filename: "QuoteProvider.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage:
|
||||
interfaces:
|
||||
Storage:
|
||||
config:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "storage.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
github.com/ultravioletrs/cocos/pkg/clients/grpc:
|
||||
interfaces:
|
||||
Client:
|
||||
config:
|
||||
dir: "{{.InterfaceDir}}/mocks"
|
||||
filename: "client.go"
|
||||
mockname: "{{.InterfaceName}}"
|
||||
|
||||
+24
-20
@@ -20,8 +20,8 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,8 +51,8 @@ var (
|
||||
errConnCreate = errors.New("could not create connection")
|
||||
)
|
||||
|
||||
type ValidationVerification func(data1, data2 []byte) error
|
||||
type FetchAttestation func(data1 []byte) ([]byte, error)
|
||||
type ValidationVerification func(data1, data2, data3, data4 []byte) error
|
||||
type FetchAttestation func(data1, data2, data3 []byte) ([]byte, error)
|
||||
|
||||
func registerFetchAttestation(callback FetchAttestation) uintptr {
|
||||
handle := cgo.NewHandle(callback)
|
||||
@@ -70,7 +70,7 @@ func validationVerificationCallback(teeType C.int) uintptr {
|
||||
case NoTee:
|
||||
return uintptr(0)
|
||||
case AmdSevSnp:
|
||||
return registerValidationVerification(quoteprovider.VerifyAttestationReportTLS)
|
||||
return registerValidationVerification(vtpm.VTPMVerify)
|
||||
default:
|
||||
return uintptr(0)
|
||||
}
|
||||
@@ -82,22 +82,24 @@ func fetchAttestationCallback(teeType C.int) uintptr {
|
||||
case NoTee:
|
||||
return uintptr(0)
|
||||
case AmdSevSnp:
|
||||
return registerFetchAttestation(quoteprovider.FetchAttestation)
|
||||
return registerFetchAttestation(vtpm.FetchATLSQuote)
|
||||
default:
|
||||
return uintptr(0)
|
||||
}
|
||||
}
|
||||
|
||||
//export callVerificationValidationCallback
|
||||
func callVerificationValidationCallback(callbackHandle uintptr, attReport *C.uchar, attReportSize C.int, repData *C.uchar) C.int {
|
||||
func callVerificationValidationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKeyLen C.int, quote *C.uchar, quoteSize C.int, teeNonce *C.uchar, nonce *C.uchar) C.int {
|
||||
handle := cgo.Handle(callbackHandle)
|
||||
defer handle.Delete()
|
||||
|
||||
callback := handle.Value().(ValidationVerification)
|
||||
attestationReport := C.GoBytes(unsafe.Pointer(attReport), attReportSize)
|
||||
reportData := C.GoBytes(unsafe.Pointer(repData), agent.ReportDataSize)
|
||||
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
|
||||
attestationReport := C.GoBytes(unsafe.Pointer(quote), quoteSize)
|
||||
teeData := C.GoBytes(unsafe.Pointer(teeNonce), quoteprovider.Nonce)
|
||||
nonceData := C.GoBytes(unsafe.Pointer(nonce), vtpm.Nonce)
|
||||
|
||||
err := callback(attestationReport, reportData)
|
||||
err := callback(attestationReport, pubKeyCert, teeData, nonceData)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "callback failed %v", err)
|
||||
return C.int(-1)
|
||||
@@ -107,20 +109,22 @@ func callVerificationValidationCallback(callbackHandle uintptr, attReport *C.uch
|
||||
}
|
||||
|
||||
//export callFetchAttestationCallback
|
||||
func callFetchAttestationCallback(callbackHandle uintptr, reportDataByte *C.uchar, outlen *C.int) *C.uchar {
|
||||
func callFetchAttestationCallback(callbackHandle uintptr, pubKey *C.uchar, pubKeyLen C.int, teeNonceByte *C.uchar, vTPMNonceByte *C.uchar, outlen *C.ulong) *C.uchar {
|
||||
handle := cgo.Handle(callbackHandle)
|
||||
defer handle.Delete()
|
||||
|
||||
callback := handle.Value().(FetchAttestation)
|
||||
reportData := C.GoBytes(unsafe.Pointer(reportDataByte), agent.ReportDataSize)
|
||||
pubKeyCert := C.GoBytes(unsafe.Pointer(pubKey), pubKeyLen)
|
||||
teeNonceData := C.GoBytes(unsafe.Pointer(teeNonceByte), quoteprovider.Nonce)
|
||||
vTPMNonce := C.GoBytes(unsafe.Pointer(vTPMNonceByte), vtpm.Nonce)
|
||||
|
||||
quote, err := callback(reportData)
|
||||
quote, err := callback(pubKeyCert, teeNonceData, vTPMNonce)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "attestation callback returned nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
*outlen = C.int(len(quote))
|
||||
*outlen = C.ulong(len(quote))
|
||||
resultC := C.malloc(C.size_t(len(quote)))
|
||||
if resultC == nil {
|
||||
fmt.Fprintf(os.Stderr, "could not allocate memory for fetch attestation callback")
|
||||
@@ -232,23 +236,23 @@ func (c *ATLSConn) Read(b []byte) (int, error) {
|
||||
case noError:
|
||||
return n, nil // no error.
|
||||
case errorZeroReturn:
|
||||
fmt.Fprintf(os.Stdout, "Connection closed by peer")
|
||||
fmt.Fprintf(os.Stdout, "Connection closed by peer\n")
|
||||
return 0, io.EOF // connection closed.
|
||||
case errorWantRead:
|
||||
fmt.Fprintf(os.Stderr, "Operation read incomplete, retry later")
|
||||
fmt.Fprintf(os.Stderr, "Operation read incomplete, retry later\n")
|
||||
return 0, nil // non-fatal, just retry later.
|
||||
case errorWantWrite:
|
||||
fmt.Fprintf(os.Stderr, "Operation write incomplete, retry later")
|
||||
fmt.Fprintf(os.Stderr, "Operation write incomplete, retry later\n")
|
||||
return 0, nil // non-fatal, just retry later.
|
||||
case errorSyscall:
|
||||
fmt.Fprintf(os.Stderr, "I/O error")
|
||||
fmt.Fprintf(os.Stderr, "I/O error\n")
|
||||
return 0, syscall.ECONNRESET // return connection reset error.
|
||||
case errorSsl:
|
||||
fmt.Fprintf(os.Stderr, "I/O error")
|
||||
fmt.Fprintf(os.Stderr, "I/O error\n")
|
||||
return 0, syscall.ECONNRESET // return connection reset error.
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "SSL error occurred: %d\n", errCode)
|
||||
return 0, fmt.Errorf("SSL error")
|
||||
return 0, fmt.Errorf("SSL error\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +301,7 @@ func (c *ATLSConn) Close() error {
|
||||
return errTLSConn
|
||||
} else if int(ret) == 1 {
|
||||
c.tlsConn = nil
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+74
-97
@@ -7,30 +7,27 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern int callVerificationValidationCallback(uintptr_t callbackHandle, const u_char* attReport, int attReportSize, const u_char* repData);
|
||||
extern u_char* callFetchAttestationCallback(uintptr_t callbackHandle, const u_char* reportDataByte, int* outlen);
|
||||
extern int callVerificationValidationCallback(uintptr_t callbackHandle, const u_char* pubKey, int pubKeyLen, const u_char* quote, int quoteSize, const u_char* teeNonce, const u_char* nonce);
|
||||
extern u_char* callFetchAttestationCallback(uintptr_t callbackHandle, const u_char* pubKey, int pubKeyLen, const u_char* teeNonceByte, const u_char* vTPMNonceByte, unsigned long* outlen);
|
||||
extern uintptr_t validationVerificationCallback(int teeType);
|
||||
extern uintptr_t fetchAttestationCallback(int teeType);
|
||||
|
||||
int triggerVerificationValidationCallback(uintptr_t callbackHandle, u_char *attestationReport, int reportSize, u_char *reportData) {
|
||||
if (attestationReport == NULL || reportData == NULL) {
|
||||
fprintf(stderr, "attestation data and report data cannot be NULL\n");
|
||||
int triggerVerificationValidationCallback(uintptr_t callbackHandle, u_char* pub_key, int pub_key_len, u_char *quote, int quote_size, u_char *tee_nonce, u_char *vtpm_nonce) {
|
||||
if (quote == NULL || vtpm_nonce == NULL || tee_nonce == NULL || pub_key == NULL) {
|
||||
fprintf(stderr, "attestation and noce and public key cannot be NULL\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
return callVerificationValidationCallback(callbackHandle, attestationReport, reportSize, reportData);
|
||||
return callVerificationValidationCallback(callbackHandle, pub_key, pub_key_len, quote, quote_size, tee_nonce, vtpm_nonce);
|
||||
}
|
||||
|
||||
u_char* triggerFetchAttestationCallback(uintptr_t callbackHandle, char *reportData) {
|
||||
int outlen = REPORT_DATA_SIZE;
|
||||
|
||||
if(reportData == NULL) {
|
||||
u_char* triggerFetchAttestationCallback(uintptr_t callback_handle, u_char* pub_key, int pub_key_len, char *tee_nonce, char *vtpm_nonce, unsigned long *outlen) {
|
||||
if(tee_nonce == NULL || vtpm_nonce == NULL) {
|
||||
fprintf(stderr, "Report data cannot be NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return callFetchAttestationCallback(callbackHandle, reportData, &outlen);
|
||||
return callFetchAttestationCallback(callback_handle, pub_key, pub_key_len, tee_nonce, vtpm_nonce, outlen);
|
||||
}
|
||||
|
||||
int check_sev_snp() {
|
||||
@@ -47,46 +44,6 @@ int check_sev_snp() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int compute_sha256_of_public_key_nonce(X509 *cert, u_char *nonce, u_char *hash) {
|
||||
EVP_PKEY *pkey = NULL;
|
||||
u_char *pubkey_buf = NULL;
|
||||
u_char *concatinated = NULL;
|
||||
int pubkey_len = 0;
|
||||
int totla_len = 0;
|
||||
|
||||
pkey = X509_get_pubkey(cert);
|
||||
if (pkey == NULL) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
|
||||
if (pubkey_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return -1;
|
||||
}
|
||||
|
||||
totla_len = pubkey_len + CLIENT_RANDOM_SIZE;
|
||||
concatinated = (u_char*)malloc(totla_len);
|
||||
if (concatinated == NULL) {
|
||||
perror("failed to allocate memory");
|
||||
return -1;
|
||||
}
|
||||
memcpy(concatinated, nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(concatinated + CLIENT_RANDOM_SIZE, pubkey_buf, pubkey_len);
|
||||
|
||||
// Compute the SHA-512 hash of the DER-encoded public key and the random nonce
|
||||
SHA512(concatinated, totla_len, hash);
|
||||
|
||||
// Clean up
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
free(concatinated);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/*
|
||||
Evidence request extension
|
||||
- Contains a random nonce that goes into the attestation report
|
||||
@@ -121,9 +78,14 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
}
|
||||
|
||||
if (ext_data != NULL) {
|
||||
if (RAND_bytes(ext_data->er.data, CLIENT_RANDOM_SIZE) != 1) {
|
||||
perror("could not generate random bytes, will use SSL client random");
|
||||
SSL_get_client_random(s, ext_data->er.data, CLIENT_RANDOM_SIZE);
|
||||
if (RAND_bytes(ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE) != 1) {
|
||||
perror("could not generate random bytes for vtpm nonce, will use SSL client random");
|
||||
SSL_get_client_random(s, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
}
|
||||
|
||||
if (RAND_bytes(ext_data->er.tee_nonce, REPORT_DATA_SIZE) != 1) {
|
||||
perror("could not generate random bytes for tee nonce, will use SSL client random");
|
||||
SSL_get_client_random(s, ext_data->er.tee_nonce, REPORT_DATA_SIZE);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "add_arg is NULL\n");
|
||||
@@ -132,7 +94,8 @@ int evidence_request_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(er->data, ext_data->er.data, CLIENT_RANDOM_SIZE);
|
||||
memcpy(er->vtpm_nonce, ext_data->er.vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(er->tee_nonce, ext_data->er.tee_nonce, REPORT_DATA_SIZE);
|
||||
er->tee_type = AMD_TEE;
|
||||
ext_data->er.tee_type = AMD_TEE;
|
||||
|
||||
@@ -201,7 +164,8 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
evidence_request *er = (evidence_request*)in;
|
||||
|
||||
if (ext_data != NULL) {
|
||||
memcpy(ext_data->er.data, er->data, CLIENT_RANDOM_SIZE);
|
||||
memcpy(ext_data->er.vtpm_nonce, er->vtpm_nonce, CLIENT_RANDOM_SIZE);
|
||||
memcpy(ext_data->er.tee_nonce, er->tee_nonce, REPORT_DATA_SIZE);
|
||||
ext_data->er.tee_type = er->tee_type;
|
||||
} else {
|
||||
fprintf(stderr, "parse_arg is NULL\n");
|
||||
@@ -238,7 +202,7 @@ int evidence_request_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
/*
|
||||
Attestation Certificate extension
|
||||
- Contains the attestation report
|
||||
- The attestation report contains the hash of the nonce and the Public Key of the x.509 Agent certificate
|
||||
- The attestation report contains the hash of the nonce, the Public Key of the x.509 Agent certificate, and the vTPM AK
|
||||
*/
|
||||
void attestation_certificate_ext_free_cb(SSL *s, unsigned int ext_type,
|
||||
unsigned int context,
|
||||
@@ -263,40 +227,46 @@ int attestation_certificate_ext_add_cb(SSL *s, unsigned int ext_type,
|
||||
{
|
||||
tls_extension_data *ext_data = (tls_extension_data*)add_arg;
|
||||
if (ext_data != NULL) {
|
||||
u_char *attestation_report;
|
||||
u_char *hash = (u_char*)malloc(REPORT_DATA_SIZE*sizeof(u_char));
|
||||
|
||||
if (hash == NULL) {
|
||||
perror("could not allocate memory");
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
return -1;
|
||||
}
|
||||
u_char *quote;
|
||||
size_t len = 0;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
u_char *pubkey_buf = NULL;
|
||||
int pubkey_len = 0;
|
||||
|
||||
|
||||
if (x != NULL) {
|
||||
int ret = compute_sha256_of_public_key_nonce(x, ext_data->er.data, hash);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "error while calculating hash\n");
|
||||
free(hash);
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
pkey = X509_get_pubkey(x);
|
||||
if (pkey == NULL) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
|
||||
if (pubkey_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "agent certificate must be used for aTLS\n");
|
||||
free(hash);
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
attestation_report = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, hash);
|
||||
if (attestation_report == NULL) {
|
||||
quote = triggerFetchAttestationCallback(ext_data->fetch_attestation_handler, pubkey_buf, pubkey_len, ext_data->er.tee_nonce, ext_data->er.vtpm_nonce, &len);
|
||||
if (quote == NULL) {
|
||||
fprintf(stderr, "attestation report is NULL\n");
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
return -1;
|
||||
}
|
||||
free(hash);
|
||||
|
||||
*out = attestation_report;
|
||||
*outlen = ATTESTATION_REPORT_SIZE;
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
|
||||
*out = quote;
|
||||
*outlen = len;
|
||||
return 1;
|
||||
} else {
|
||||
fprintf(stderr, "add_arg is NULL\n");
|
||||
@@ -329,34 +299,41 @@ int attestation_certificate_ext_parse_cb(SSL *s, unsigned int ext_type,
|
||||
tls_extension_data *ext_data = (tls_extension_data*)parse_arg;
|
||||
|
||||
if (ext_data != NULL) {
|
||||
char *attestation_report = (char*)malloc(ATTESTATION_REPORT_SIZE*sizeof(char));
|
||||
u_char *hash = (u_char*)malloc(REPORT_DATA_SIZE*sizeof(u_char));
|
||||
char *quote = (char*)malloc(inlen*sizeof(char));
|
||||
EVP_PKEY *pkey = NULL;
|
||||
u_char *pubkey_buf = NULL;
|
||||
int pubkey_len = 0;
|
||||
int res = 0;
|
||||
|
||||
if (hash == NULL || attestation_report == NULL) {
|
||||
if (quote == NULL) {
|
||||
perror("could not allocate memory");
|
||||
|
||||
if (hash != NULL) free(hash);
|
||||
if (attestation_report != NULL) free(attestation_report);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (compute_sha256_of_public_key_nonce(x, ext_data->er.data, hash) != 0) {
|
||||
fprintf(stderr, "calculating hash failed\n");
|
||||
free(attestation_report);
|
||||
free(hash);
|
||||
return 0;
|
||||
pkey = X509_get_pubkey(x);
|
||||
if (pkey == NULL) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pubkey_len = i2d_PUBKEY(pkey, &pubkey_buf);
|
||||
if (pubkey_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return -1;
|
||||
}
|
||||
memcpy(quote, in, inlen);
|
||||
|
||||
memcpy(attestation_report, in, inlen);
|
||||
|
||||
res = triggerVerificationValidationCallback(ext_data->verification_validation_handler,
|
||||
attestation_report,
|
||||
ATTESTATION_REPORT_SIZE,
|
||||
hash);
|
||||
free(attestation_report);
|
||||
free(hash);
|
||||
res = triggerVerificationValidationCallback(ext_data->verification_validation_handler,
|
||||
pubkey_buf,
|
||||
pubkey_len,
|
||||
quote,
|
||||
inlen,
|
||||
(u_char*)&ext_data->er.tee_nonce,
|
||||
(u_char*)&ext_data->er.vtpm_nonce);
|
||||
free(quote);
|
||||
EVP_PKEY_free(pkey);
|
||||
OPENSSL_free(pubkey_buf);
|
||||
|
||||
if (res != 0) {
|
||||
fprintf(stderr, "verification and validation failed, aborting connection\n");
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#define EVIDENCE_REQUEST_HELLO_EXTENSION_TYPE 65
|
||||
#define ATTESTATION_CERTIFICATE_EXTENSION_TYPE 66
|
||||
#define ATTESTATION_REPORT_SIZE 0x4A0
|
||||
#define REPORT_DATA_SIZE 64
|
||||
#define CLIENT_RANDOM_SIZE 32
|
||||
#define TLS_CLIENT_CTX 0
|
||||
@@ -19,7 +18,8 @@
|
||||
typedef struct evidence_request
|
||||
{
|
||||
int tee_type;
|
||||
char data[CLIENT_RANDOM_SIZE];
|
||||
char vtpm_nonce[CLIENT_RANDOM_SIZE];
|
||||
char tee_nonce[REPORT_DATA_SIZE];
|
||||
} evidence_request;
|
||||
|
||||
typedef struct tls_extension_data
|
||||
@@ -32,7 +32,7 @@ typedef struct tls_extension_data
|
||||
typedef struct tls_server_connection
|
||||
{
|
||||
int server_fd;
|
||||
char* cert;
|
||||
char* cert;
|
||||
int cert_len;
|
||||
char* key;
|
||||
int key_len;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package cmdconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var IgvmMeasureOptions = []string{"measure", "-b"}
|
||||
|
||||
type MeasurementProvider interface {
|
||||
Run(binaryPath string) ([]byte, error)
|
||||
Stop() error
|
||||
}
|
||||
type CmdConfig struct {
|
||||
binPath string
|
||||
options []string
|
||||
stderr io.Writer
|
||||
cmd *exec.Cmd
|
||||
execCommand func(name string, arg ...string) *exec.Cmd
|
||||
}
|
||||
|
||||
func NewCmdConfig(binPath string, options []string, stderr io.Writer) (*CmdConfig, error) {
|
||||
if binPath == "" {
|
||||
return nil, fmt.Errorf("pathToBinary cannot be empty")
|
||||
}
|
||||
|
||||
return &CmdConfig{
|
||||
binPath: binPath,
|
||||
options: options,
|
||||
stderr: stderr,
|
||||
execCommand: exec.Command,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *CmdConfig) Run(pathToFile string) ([]byte, error) {
|
||||
binary := m.binPath
|
||||
args := []string{}
|
||||
args = append(args, pathToFile)
|
||||
args = append(args, m.options...)
|
||||
|
||||
outBuf := &bytes.Buffer{}
|
||||
cmd := m.execCommand(binary, args...)
|
||||
cmd.Stderr = m.stderr
|
||||
cmd.Stdout = outBuf
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return outBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (m *CmdConfig) Stop() error {
|
||||
if m.cmd == nil || m.cmd.Process == nil {
|
||||
return fmt.Errorf("no running process to stop")
|
||||
}
|
||||
|
||||
if err := m.cmd.Process.Kill(); err != nil {
|
||||
return fmt.Errorf("failed to stop process: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetExecCommand allows tests to inject a mock execCommand function.
|
||||
func (m *CmdConfig) SetExecCommand(cmdFunc func(name string, arg ...string) *exec.Cmd) {
|
||||
m.execCommand = cmdFunc
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package cmdconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIgvmMeasurement(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func() *CmdConfig
|
||||
runArgs string
|
||||
expectErr bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "NewIgvmMeasurement - Empty pathToBinary",
|
||||
setup: func() *CmdConfig {
|
||||
igvm, err := NewCmdConfig("", []string{""}, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, igvm)
|
||||
return nil
|
||||
},
|
||||
expectErr: true,
|
||||
expectedErr: "pathToBinary cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "Run - Successful Execution",
|
||||
setup: func() *CmdConfig {
|
||||
igvm, _ := NewCmdConfig("/valid/path", []string{""}, nil)
|
||||
igvm.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
|
||||
return exec.Command("sh", "-c", "echo 'measurement successful'")
|
||||
})
|
||||
return igvm
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Run - Failure Execution",
|
||||
setup: func() *CmdConfig {
|
||||
igvm, _ := NewCmdConfig("/invalid/path", []string{""}, nil)
|
||||
igvm.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
|
||||
return exec.Command("sh", "-c", "echo 'some error occurred\nextra line' && exit 1")
|
||||
})
|
||||
return igvm
|
||||
},
|
||||
expectErr: true,
|
||||
expectedErr: "exit status 1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
igvm := tc.setup()
|
||||
|
||||
if igvm != nil {
|
||||
buf := new(bytes.Buffer)
|
||||
igvm.stderr = buf
|
||||
|
||||
_, err := igvm.Run(tc.runArgs)
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tc.expectedErr), strings.TrimSpace(err.Error()))
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type AttestationType int32
|
||||
|
||||
const (
|
||||
SNP AttestationType = iota
|
||||
VTPM
|
||||
SNPvTPM
|
||||
)
|
||||
|
||||
var (
|
||||
AttestationPolicy = Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &PcrConfig{}}
|
||||
ErrAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
|
||||
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
|
||||
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
|
||||
ErrAttestationPolicyEncode = errors.New("failed to encode the Attestation Policy")
|
||||
)
|
||||
|
||||
type PcrValues struct {
|
||||
Sha256 map[string]string `json:"sha256"`
|
||||
Sha384 map[string]string `json:"sha384"`
|
||||
Sha1 map[string]string `json:"sha1"`
|
||||
}
|
||||
|
||||
type PcrConfig struct {
|
||||
PCRValues PcrValues `json:"pcr_values"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
*check.Config
|
||||
*PcrConfig
|
||||
}
|
||||
|
||||
func ReadAttestationPolicy(policyPath string, attestationConfiguration *Config) error {
|
||||
if policyPath != "" {
|
||||
policyData, err := os.ReadFile(policyPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyOpen, err)
|
||||
}
|
||||
|
||||
return ReadAttestationPolicyFromByte(policyData, attestationConfiguration)
|
||||
}
|
||||
|
||||
return ErrAttestationPolicyMissing
|
||||
}
|
||||
|
||||
func ReadAttestationPolicyFromByte(policyData []byte, attestationConfiguration *Config) error {
|
||||
unmarshalOptions := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
|
||||
|
||||
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.Config); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(policyData, attestationConfiguration.PcrConfig); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/google/gce-tcb-verifier/proto/endorsement"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
const (
|
||||
// Offset of the 384-bit measurement in the report.
|
||||
// The measurement is 48 bytes long and starts at offset 0x90.
|
||||
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf page 56
|
||||
measurementOffset = 0x90
|
||||
measurementSize = 48
|
||||
bucketName = "gce_tcb_integrity"
|
||||
objectName = "ovmf_x64_csm/sevsnp/%s.binarypb"
|
||||
ovmfObjectName = "ovmf_x64_csm/%s.fd"
|
||||
)
|
||||
|
||||
func Extract384BitMeasurement(attestation *sevsnp.Attestation) (string, error) {
|
||||
if attestation == nil {
|
||||
return "", fmt.Errorf("report is nil")
|
||||
}
|
||||
|
||||
reportBin, err := report.Transform(attestation, "bin")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to transform report to binary: %v", err)
|
||||
}
|
||||
|
||||
if len(reportBin) < measurementOffset+measurementSize {
|
||||
return "", fmt.Errorf("report is too short to contain the 384-bit measurement")
|
||||
}
|
||||
|
||||
measurement := reportBin[measurementOffset : measurementOffset+measurementSize]
|
||||
return fmt.Sprintf("%x", measurement), nil
|
||||
}
|
||||
|
||||
func GetLaunchEndorsement(ctx context.Context, measurement384 string) (*endorsement.VMGoldenMeasurement, error) {
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to create storage client: %v", err)
|
||||
}
|
||||
|
||||
reader, err := client.Bucket(bucketName).Object(fmt.Sprintf(objectName, measurement384)).NewReader(ctx)
|
||||
if err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to create reader: %v", err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
launchEndorsements, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to read object: %v", err)
|
||||
}
|
||||
|
||||
var endorsementPB endorsement.VMLaunchEndorsement
|
||||
if err := proto.Unmarshal(launchEndorsements, &endorsementPB); err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to unmarshal launch endorsement: %v", err)
|
||||
}
|
||||
|
||||
var goldenUEFI endorsement.VMGoldenMeasurement
|
||||
if err := proto.Unmarshal(endorsementPB.SerializedUefiGolden, &goldenUEFI); err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to unmarshal golden UEFI: %v", err)
|
||||
}
|
||||
|
||||
return &goldenUEFI, nil
|
||||
}
|
||||
|
||||
func GenerateAttestationPolicy(endorsement *endorsement.VMGoldenMeasurement, vcpuNum uint32) (*config.Config, error) {
|
||||
attestationPolicy := config.Config{PcrConfig: &config.PcrConfig{}, Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}}
|
||||
attestationPolicy.Config.Policy.Policy = endorsement.SevSnp.Policy
|
||||
attestationPolicy.Config.Policy.Measurement = endorsement.SevSnp.Measurements[vcpuNum]
|
||||
attestationPolicy.Config.RootOfTrust.DisallowNetwork = false
|
||||
attestationPolicy.Config.RootOfTrust.CheckCrl = true
|
||||
attestationPolicy.Config.RootOfTrust.Product = "Milan"
|
||||
attestationPolicy.Config.RootOfTrust.ProductLine = "Milan"
|
||||
|
||||
return &attestationPolicy, nil
|
||||
}
|
||||
|
||||
func DownloadOvmfFile(ctx context.Context, digest string) ([]byte, error) {
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to create storage client: %v", err)
|
||||
}
|
||||
|
||||
reader, err := client.Bucket(bucketName).Object(fmt.Sprintf(ovmfObjectName, digest)).NewReader(ctx)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to create reader: %v", err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
ovmf, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to read object: %v", err)
|
||||
}
|
||||
|
||||
return ovmf, nil
|
||||
}
|
||||
@@ -8,26 +8,24 @@ package quoteprovider
|
||||
|
||||
import (
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
pb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
cocosai "github.com/ultravioletrs/cocos"
|
||||
)
|
||||
|
||||
var (
|
||||
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
)
|
||||
const Nonce = 64
|
||||
|
||||
var _ client.QuoteProvider = (*embeddedQuoteProvider)(nil)
|
||||
var _ client.LeveledQuoteProvider = (*embeddedQuoteProvider)(nil)
|
||||
|
||||
type embeddedQuoteProvider struct {
|
||||
}
|
||||
|
||||
func GetQuoteProvider() (client.QuoteProvider, error) {
|
||||
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
return &embeddedQuoteProvider{}, nil
|
||||
}
|
||||
|
||||
// GetQuote returns the SEV quote for the given report data.
|
||||
func (e *embeddedQuoteProvider) GetRawQuote(reportData [64]byte) ([]byte, error) {
|
||||
// GetRawQuoteAtLevel returns the SEV quote for the given report data and VMPL.
|
||||
func (e *embeddedQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
@@ -46,6 +44,6 @@ func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
return cocosai.EmbeddedAttestation, nil
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error {
|
||||
func VerifyAttestationReportTLS(attestation *sevsnp.Attestation, reportData []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// Code generated by mockery v2.45.0. DO NOT EDIT.
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by mockery v2.53.3. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@@ -7,34 +10,42 @@ import (
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// QuoteProvider is an autogenerated mock type for the QuoteProvider type
|
||||
type QuoteProvider struct {
|
||||
// LeveledQuoteProvider is an autogenerated mock type for the LeveledQuoteProvider type
|
||||
type LeveledQuoteProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetRawQuote provides a mock function with given fields: reportData
|
||||
func (_m *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
|
||||
ret := _m.Called(reportData)
|
||||
type LeveledQuoteProvider_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *LeveledQuoteProvider) EXPECT() *LeveledQuoteProvider_Expecter {
|
||||
return &LeveledQuoteProvider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetRawQuoteAtLevel provides a mock function with given fields: reportData, vmpl
|
||||
func (_m *LeveledQuoteProvider) GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error) {
|
||||
ret := _m.Called(reportData, vmpl)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRawQuote")
|
||||
panic("no return value specified for GetRawQuoteAtLevel")
|
||||
}
|
||||
|
||||
var r0 []uint8
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([64]byte) ([]uint8, error)); ok {
|
||||
return rf(reportData)
|
||||
if rf, ok := ret.Get(0).(func([64]byte, uint) ([]uint8, error)); ok {
|
||||
return rf(reportData, vmpl)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([64]byte) []uint8); ok {
|
||||
r0 = rf(reportData)
|
||||
if rf, ok := ret.Get(0).(func([64]byte, uint) []uint8); ok {
|
||||
r0 = rf(reportData, vmpl)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]uint8)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([64]byte) error); ok {
|
||||
r1 = rf(reportData)
|
||||
if rf, ok := ret.Get(1).(func([64]byte, uint) error); ok {
|
||||
r1 = rf(reportData, vmpl)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
@@ -42,8 +53,37 @@ func (_m *QuoteProvider) GetRawQuote(reportData [64]byte) ([]uint8, error) {
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// IsSupported provides a mock function with given fields:
|
||||
func (_m *QuoteProvider) IsSupported() bool {
|
||||
// LeveledQuoteProvider_GetRawQuoteAtLevel_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRawQuoteAtLevel'
|
||||
type LeveledQuoteProvider_GetRawQuoteAtLevel_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetRawQuoteAtLevel is a helper method to define mock.On call
|
||||
// - reportData [64]byte
|
||||
// - vmpl uint
|
||||
func (_e *LeveledQuoteProvider_Expecter) GetRawQuoteAtLevel(reportData interface{}, vmpl interface{}) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
return &LeveledQuoteProvider_GetRawQuoteAtLevel_Call{Call: _e.mock.On("GetRawQuoteAtLevel", reportData, vmpl)}
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Run(run func(reportData [64]byte, vmpl uint)) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([64]byte), args[1].(uint))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) Return(_a0 []uint8, _a1 error) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_GetRawQuoteAtLevel_Call) RunAndReturn(run func([64]byte, uint) ([]uint8, error)) *LeveledQuoteProvider_GetRawQuoteAtLevel_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// IsSupported provides a mock function with no fields
|
||||
func (_m *LeveledQuoteProvider) IsSupported() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -60,8 +100,35 @@ func (_m *QuoteProvider) IsSupported() bool {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Product provides a mock function with given fields:
|
||||
func (_m *QuoteProvider) Product() *sevsnp.SevProduct {
|
||||
// LeveledQuoteProvider_IsSupported_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsSupported'
|
||||
type LeveledQuoteProvider_IsSupported_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsSupported is a helper method to define mock.On call
|
||||
func (_e *LeveledQuoteProvider_Expecter) IsSupported() *LeveledQuoteProvider_IsSupported_Call {
|
||||
return &LeveledQuoteProvider_IsSupported_Call{Call: _e.mock.On("IsSupported")}
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) Run(run func()) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) Return(_a0 bool) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_IsSupported_Call) RunAndReturn(run func() bool) *LeveledQuoteProvider_IsSupported_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Product provides a mock function with no fields
|
||||
func (_m *LeveledQuoteProvider) Product() *sevsnp.SevProduct {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
@@ -80,13 +147,40 @@ func (_m *QuoteProvider) Product() *sevsnp.SevProduct {
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewQuoteProvider creates a new instance of QuoteProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// LeveledQuoteProvider_Product_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Product'
|
||||
type LeveledQuoteProvider_Product_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Product is a helper method to define mock.On call
|
||||
func (_e *LeveledQuoteProvider_Expecter) Product() *LeveledQuoteProvider_Product_Call {
|
||||
return &LeveledQuoteProvider_Product_Call{Call: _e.mock.On("Product")}
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_Product_Call) Run(run func()) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_Product_Call) Return(_a0 *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *LeveledQuoteProvider_Product_Call) RunAndReturn(run func() *sevsnp.SevProduct) *LeveledQuoteProvider_Product_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewLeveledQuoteProvider creates a new instance of LeveledQuoteProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewQuoteProvider(t interface {
|
||||
func NewLeveledQuoteProvider(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *QuoteProvider {
|
||||
mock := &QuoteProvider{}
|
||||
}) *LeveledQuoteProvider {
|
||||
mock := &LeveledQuoteProvider{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-sev-guest/client"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"github.com/google/go-sev-guest/verify"
|
||||
"github.com/google/go-sev-guest/verify/trust"
|
||||
"github.com/google/logger"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -29,20 +29,19 @@ const (
|
||||
cocosDirectory = ".cocos"
|
||||
caBundleName = "ask_ark.pem"
|
||||
attestationReportSize = 0x4A0
|
||||
reportDataSize = 64
|
||||
Nonce = 64
|
||||
sevProductNameMilan = "Milan"
|
||||
sevProductNameGenoa = "Genoa"
|
||||
sevVMPL = 2
|
||||
)
|
||||
|
||||
var (
|
||||
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
timeout = time.Minute * 2
|
||||
maxTryDelay = time.Second * 30
|
||||
timeout = time.Minute * 2
|
||||
maxTryDelay = time.Second * 30
|
||||
)
|
||||
|
||||
var (
|
||||
errProductLine = errors.New(fmt.Sprintf("product name must be %s or %s", sevProductNameMilan, sevProductNameGenoa))
|
||||
errReportSize = errors.New("attestation report size mismatch")
|
||||
errAttVerification = errors.New("attestation verification failed")
|
||||
errAttValidation = errors.New("attestation validation failed")
|
||||
)
|
||||
@@ -138,38 +137,31 @@ func validateReport(attestationPB *sevsnp.Attestation, cfg *check.Config) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetQuoteProvider() (client.QuoteProvider, error) {
|
||||
return client.GetQuoteProvider()
|
||||
func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
return client.GetLeveledQuoteProvider()
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error {
|
||||
config, err := copyConfig(&AttConfigurationSEVSNP)
|
||||
func VerifyAttestationReportTLS(attestationPB *sevsnp.Attestation, reportData []byte) error {
|
||||
config, err := copyConfig(config.AttestationPolicy.Config)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to create a copy of attestation policy"), err)
|
||||
}
|
||||
|
||||
// Certificate chain is populated based on the extra data that is appended to the SEV-SNP attestation report.
|
||||
// This data is not part of the attestation report and it will be ignored.
|
||||
attestationPB.CertificateChain = nil
|
||||
config.Policy.ReportData = reportData[:]
|
||||
return VerifyAndValidate(attestationBytes, config)
|
||||
return VerifyAndValidate(attestationPB, config)
|
||||
}
|
||||
|
||||
func VerifyAndValidate(attestationReport []byte, cfg *check.Config) error {
|
||||
func VerifyAndValidate(attestationPB *sevsnp.Attestation, cfg *check.Config) error {
|
||||
logger.Init("", false, false, io.Discard)
|
||||
|
||||
if len(attestationReport) < attestationReportSize {
|
||||
return errReportSize
|
||||
}
|
||||
attestationBytes := attestationReport[:attestationReportSize]
|
||||
|
||||
attestationPB, err := abi.ReportCertsToProto(attestationBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert attestation bytes to struct %v", errors.Wrap(errAttVerification, err))
|
||||
}
|
||||
|
||||
if err = verifyReport(attestationPB, cfg); err != nil {
|
||||
if err := verifyReport(attestationPB, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = validateReport(attestationPB, cfg); err != nil {
|
||||
if err := validateReport(attestationPB, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -177,19 +169,19 @@ func VerifyAndValidate(attestationReport []byte, cfg *check.Config) error {
|
||||
}
|
||||
|
||||
func FetchAttestation(reportDataSlice []byte) ([]byte, error) {
|
||||
var reportData [reportDataSize]byte
|
||||
var reportData [Nonce]byte
|
||||
|
||||
qp, err := GetQuoteProvider()
|
||||
qp, err := GetLeveledQuoteProvider()
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("could not get quote provider")
|
||||
}
|
||||
|
||||
if len(reportData) > reportDataSize {
|
||||
if len(reportData) > Nonce {
|
||||
return []byte{}, fmt.Errorf("attestation report size mismatch")
|
||||
}
|
||||
copy(reportData[:], reportDataSlice)
|
||||
|
||||
rawQuote, err := qp.GetRawQuote(reportData)
|
||||
rawQuote, err := qp.GetRawQuoteAtLevel(reportData, sevVMPL)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to get raw quote")
|
||||
}
|
||||
|
||||
@@ -17,14 +17,10 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const (
|
||||
measurementOffset = 0x90
|
||||
signatureOffset = 0x2A0
|
||||
)
|
||||
|
||||
func TestFillInAttestationLocal(t *testing.T) {
|
||||
tempDir, err := os.MkdirTemp("", "test_home")
|
||||
require.NoError(t, err)
|
||||
@@ -76,18 +72,18 @@ func TestFillInAttestationLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportSuccess(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
goodProduct int
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, validation and verification is performed succsessfully",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
goodProduct: 1,
|
||||
err: nil,
|
||||
@@ -103,20 +99,20 @@ func TestVerifyAttestationReportSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportMalformedSignature(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
// Change random data so in the signature so the signature failes
|
||||
file[signatureOffset] = file[signatureOffset] ^ 0x01
|
||||
attestationPB.Report.Signature[0] = attestationPB.Report.Signature[0] ^ 0x01
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, distorted signature",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
err: errAttVerification,
|
||||
},
|
||||
@@ -131,17 +127,17 @@ func TestVerifyAttestationReportMalformedSignature(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, unknown product",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
err: errProductLine,
|
||||
},
|
||||
@@ -149,8 +145,8 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AttConfigurationSEVSNP.RootOfTrust.ProductLine = ""
|
||||
AttConfigurationSEVSNP.Policy.Product = nil
|
||||
config.AttestationPolicy.Config.RootOfTrust.ProductLine = ""
|
||||
config.AttestationPolicy.Config.Policy.Product = nil
|
||||
err := VerifyAttestationReportTLS(tt.attestationReport, tt.reportData)
|
||||
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
|
||||
})
|
||||
@@ -158,20 +154,20 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVerifyAttestationReportMalformedPolicy(t *testing.T) {
|
||||
file, reportData := prepareForTestVerifyAttestationReport(t)
|
||||
attestationPB, reportData := prepVerifyAttReport(t)
|
||||
|
||||
// Change random data in the measurement so the measurement does not match
|
||||
file[measurementOffset] = file[measurementOffset] ^ 0x01
|
||||
attestationPB.Report.Measurement[0] = attestationPB.Report.Measurement[0] ^ 0x01
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
attestationReport []byte
|
||||
attestationReport *sevsnp.Attestation
|
||||
reportData []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid attestation, malformed policy (measurement)",
|
||||
attestationReport: file,
|
||||
attestationReport: attestationPB,
|
||||
reportData: reportData,
|
||||
err: errAttVerification,
|
||||
},
|
||||
@@ -185,32 +181,34 @@ func TestVerifyAttestationReportMalformedPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func prepareForTestVerifyAttestationReport(t *testing.T) ([]byte, []byte) {
|
||||
func prepVerifyAttReport(t *testing.T) (*sevsnp.Attestation, []byte) {
|
||||
file, err := os.ReadFile("../../../attestation.bin")
|
||||
require.NoError(t, err)
|
||||
|
||||
rr, err := abi.ReportCertsToProto(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(file) < attestationReportSize {
|
||||
file = append(file, make([]byte, attestationReportSize-len(file))...)
|
||||
}
|
||||
|
||||
AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
rr, err := abi.ReportCertsToProto(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
config.AttestationPolicy = config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
attestationPolicyFile, err := os.ReadFile("../../../scripts/attestation_policy/attestation_policy.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = protojson.Unmarshal(attestationPolicyFile, &AttConfigurationSEVSNP)
|
||||
unmarshalOptions := protojson.UnmarshalOptions{DiscardUnknown: true}
|
||||
|
||||
err = unmarshalOptions.Unmarshal(attestationPolicyFile, config.AttestationPolicy.Config)
|
||||
require.NoError(t, err)
|
||||
|
||||
AttConfigurationSEVSNP.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
|
||||
AttConfigurationSEVSNP.Policy.FamilyId = rr.Report.FamilyId
|
||||
AttConfigurationSEVSNP.Policy.ImageId = rr.Report.ImageId
|
||||
AttConfigurationSEVSNP.Policy.Measurement = rr.Report.Measurement
|
||||
AttConfigurationSEVSNP.Policy.HostData = rr.Report.HostData
|
||||
AttConfigurationSEVSNP.Policy.ReportIdMa = rr.Report.ReportIdMa
|
||||
AttConfigurationSEVSNP.RootOfTrust.ProductLine = sevProductNameMilan
|
||||
config.AttestationPolicy.Config.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
|
||||
config.AttestationPolicy.Config.Policy.FamilyId = rr.Report.FamilyId
|
||||
config.AttestationPolicy.Config.Policy.ImageId = rr.Report.ImageId
|
||||
config.AttestationPolicy.Config.Policy.Measurement = rr.Report.Measurement
|
||||
config.AttestationPolicy.Config.Policy.HostData = rr.Report.HostData
|
||||
config.AttestationPolicy.Config.Policy.ReportIdMa = rr.Report.ReportIdMa
|
||||
config.AttestationPolicy.Config.RootOfTrust.ProductLine = sevProductNameMilan
|
||||
|
||||
return file, rr.Report.ReportData
|
||||
return rr, rr.Report.ReportData
|
||||
}
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package vtpm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
"github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/proto/tpm"
|
||||
"github.com/google/go-tpm-tools/server"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements"
|
||||
Nonce = 32
|
||||
PCR15 = 15
|
||||
Hash256 = 32
|
||||
Hash384 = 48
|
||||
)
|
||||
|
||||
var (
|
||||
ExternalTPM io.ReadWriteCloser
|
||||
ErrNoHashAlgo = errors.New("hash algo is not supported")
|
||||
)
|
||||
|
||||
type VtpmAttest func(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error)
|
||||
|
||||
type tpmWrapper struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (et tpmWrapper) EventLog() ([]byte, error) {
|
||||
return os.ReadFile(eventLog)
|
||||
}
|
||||
|
||||
func OpenTpm() (io.ReadWriteCloser, error) {
|
||||
if ExternalTPM != nil {
|
||||
return tpmWrapper{ExternalTPM}, nil
|
||||
}
|
||||
|
||||
tw := tpmWrapper{}
|
||||
var err error
|
||||
|
||||
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpmrm0")
|
||||
if os.IsNotExist(err) {
|
||||
tw.ReadWriteCloser, err = tpm2.OpenTPM("/dev/tpm0")
|
||||
}
|
||||
|
||||
return tw, err
|
||||
}
|
||||
|
||||
func ExtendPCR(pcrIndex int, value []byte) error {
|
||||
rwc, err := OpenTpm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rwc.Close()
|
||||
|
||||
fixedSha256Hash := sha3.Sum256(value)
|
||||
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA256, fixedSha256Hash[:], ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fixedSha384Hash := sha3.Sum384(value)
|
||||
if err := tpm2.PCRExtend(rwc, tpmutil.Handle(pcrIndex), tpm2.AlgSHA384, fixedSha384Hash[:], ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
|
||||
attestation, err := fetchVTPMQuote(vTPMNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
if teeAttestaion {
|
||||
attestation, err = addTEEAttestation(attestation, teeNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return marshalQuote(attestation)
|
||||
}
|
||||
|
||||
func FetchATLSQuote(pubKey, teeNonce, vTPMNonce []byte) ([]byte, error) {
|
||||
attestation, err := fetchVTPMQuote(vTPMNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKey, attestation.GetAkPub(), teeNonce)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
attestation, err = addTEEAttestation(attestation, reportData)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return marshalQuote(attestation)
|
||||
}
|
||||
|
||||
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte) error {
|
||||
attestation := &attest.Attestation{}
|
||||
|
||||
err := proto.Unmarshal(quote, attestation)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
|
||||
}
|
||||
|
||||
ak := attestation.GetAkPub()
|
||||
pub, err := tpm2.DecodePublic(ak)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cryptoPub, err := pub.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKeyTLS, ak, teeNonce)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to create TEE attestation report nonce"), err)
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), reportData); err != nil {
|
||||
return fmt.Errorf("failed to verify TEE attestation report: %v", err)
|
||||
}
|
||||
|
||||
_, err = server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: vtpmNonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to verify attestation"), err)
|
||||
}
|
||||
|
||||
s256, s384 := calculatePCRTLSKey(pubKeyTLS)
|
||||
|
||||
if err := checkExpectedPCRValues(attestation, s256, s384); err != nil {
|
||||
return fmt.Errorf("PCR values do not match expected PCR values: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyAttest is a dummy attestation function that returns an empty attestation report.
|
||||
func EmptyAttest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool) ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func publicKeyToBytes(pubKey interface{}) ([]byte, error) {
|
||||
derBytes, err := x509.MarshalPKIXPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return derBytes, nil
|
||||
}
|
||||
|
||||
func createTEEAttestationReportNonce(pubKeyTLS []byte, ak []byte, nonce []byte) ([]byte, error) {
|
||||
pub, err := tpm2.DecodePublic(ak)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
cryptoPub, err := pub.Key()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
pubKeyBytes, err := publicKeyToBytes(cryptoPub)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
reportData := append(append(pubKeyTLS, pubKeyBytes...), nonce...)
|
||||
hash := sha3.Sum512(reportData)
|
||||
|
||||
return hash[:], nil
|
||||
}
|
||||
|
||||
func marshalQuote(attestation *attest.Attestation) ([]byte, error) {
|
||||
out, err := proto.Marshal(attestation)
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrap(fmt.Errorf("failed to marshal vTPM attestation report"), err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
rwc, err := OpenTpm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rwc.Close()
|
||||
|
||||
attestationKey, err := client.AttestationKeyRSA(rwc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to create attestation key: %v", err), err)
|
||||
}
|
||||
defer attestationKey.Close()
|
||||
|
||||
var fixedNonce [Nonce]byte
|
||||
copy(fixedNonce[:], nonce)
|
||||
attestOpts := client.AttestOpts{}
|
||||
attestOpts.Nonce = fixedNonce[:]
|
||||
|
||||
attestOpts.TCGEventLog, err = client.GetEventLog(rwc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to retrieve TCG Event Log: %v", err), err)
|
||||
}
|
||||
|
||||
attestation, err := attestationKey.Attest(attestOpts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to attest: %v", err), err)
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func addTEEAttestation(attestation *attest.Attestation, nonce []byte) (*attest.Attestation, error) {
|
||||
rawTeeAttestation, err := quoteprovider.FetchAttestation(nonce)
|
||||
if err != nil {
|
||||
return attestation, fmt.Errorf("failed to fetch TEE attestation report: %v", err)
|
||||
}
|
||||
|
||||
extReport, err := abi.ReportCertsToProto(rawTeeAttestation)
|
||||
if err != nil {
|
||||
return attestation, errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
||||
}
|
||||
attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
|
||||
SevSnpAttestation: extReport,
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256, ePcr384 []byte) error {
|
||||
quotes := attestation.GetQuotes()
|
||||
for i := range quotes {
|
||||
quote := quotes[i]
|
||||
var pcrMap map[string]string
|
||||
var pcr15 []byte
|
||||
switch quote.Pcrs.Hash {
|
||||
case tpm.HashAlgo_SHA256:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha256
|
||||
pcr15 = ePcr256
|
||||
case tpm.HashAlgo_SHA384:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha384
|
||||
pcr15 = ePcr384
|
||||
case tpm.HashAlgo_SHA1:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha1
|
||||
pcr15 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
default:
|
||||
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
|
||||
}
|
||||
|
||||
pcr15Index := uint32(15)
|
||||
if !bytes.Equal(quote.Pcrs.Pcrs[pcr15Index], pcr15) {
|
||||
return fmt.Errorf("for algo %s PCR[15] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], hex.EncodeToString(pcr15), hex.EncodeToString(quote.Pcrs.Pcrs[pcr15Index]))
|
||||
}
|
||||
|
||||
for i, v := range pcrMap {
|
||||
index, err := strconv.ParseInt(i, 10, 32)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("error converting PCR index to int32"), err)
|
||||
}
|
||||
value, err := hex.DecodeString(v)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("error converting PCR value to byte"), err)
|
||||
}
|
||||
if !bytes.Equal(quote.Pcrs.Pcrs[uint32(index)], value) {
|
||||
return fmt.Errorf("for algo %s PCR[%d] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], index, hex.EncodeToString(value), hex.EncodeToString(quote.Pcrs.Pcrs[uint32(index)]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return SHA256 and SHA384 values of the input public key.
|
||||
func calculatePCRTLSKey(pubKey []byte) ([]byte, []byte) {
|
||||
init256 := make([]byte, Hash256)
|
||||
init384 := make([]byte, Hash384)
|
||||
|
||||
key256 := sha3.Sum256(pubKey)
|
||||
key384 := sha3.Sum384(pubKey)
|
||||
|
||||
pcrValue256 := append(init256, key256[:]...)
|
||||
pcrValue384 := append(init384, key384[:]...)
|
||||
|
||||
newPcr256 := sha256.Sum256(pcrValue256)
|
||||
newPcr384 := sha512.Sum384(pcrValue384)
|
||||
|
||||
return newPcr256[:], newPcr384[:]
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
|
||||
"github.com/ultravioletrs/cocos/agent/mocks"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
pkggrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
@@ -101,7 +102,7 @@ func TestAgentClientIntegration(t *testing.T) {
|
||||
Timeout: 1,
|
||||
},
|
||||
},
|
||||
err: ErrAgentServiceUnavailable,
|
||||
err: errors.New("failed to connect to grpc server"),
|
||||
},
|
||||
{
|
||||
name: "invalid config, missing AttestationPolicy with aTLS",
|
||||
@@ -112,7 +113,7 @@ func TestAgentClientIntegration(t *testing.T) {
|
||||
},
|
||||
AttestedTLS: true,
|
||||
},
|
||||
err: pkggrpc.ErrAttestationPolicyMissing,
|
||||
err: config.ErrAttestationPolicyMissing,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build cgo
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error) {
|
||||
err := config.ReadAttestationPolicy(cfg.AttestationPolicy, &config.AttestationPolicy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
|
||||
}
|
||||
|
||||
var insecureSkipVerify bool = true
|
||||
var rootCAs *x509.CertPool = nil
|
||||
|
||||
if len(cfg.ServerCAFile) > 0 {
|
||||
insecureSkipVerify = false
|
||||
|
||||
// Read the certificate file
|
||||
certPEM, err := os.ReadFile(cfg.ServerCAFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to read certificate file"), err)
|
||||
}
|
||||
|
||||
// Decode the PEM block
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("failed to decode PEM block")
|
||||
}
|
||||
|
||||
// Parse the certificate
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to parse certificate"), err)
|
||||
}
|
||||
|
||||
rootCAs = x509.NewCertPool()
|
||||
rootCAs.AddCert(cert)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: insecureSkipVerify,
|
||||
RootCAs: rootCAs,
|
||||
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
return verifyPeerCertificateATLS(rawCerts, verifiedChains, cfg)
|
||||
},
|
||||
}
|
||||
return credentials.NewTLS(tlsConfig), nil
|
||||
}
|
||||
|
||||
func CustomDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||
ip, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create a custom dialer")
|
||||
}
|
||||
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad format of IP address: %v", err)
|
||||
}
|
||||
|
||||
conn, err := atls.DialTLSClient(ip, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create TLS connection")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func verifyPeerCertificateATLS(rawCerts [][]byte, verifiedChains [][]*x509.Certificate, cfg AgentClientConfig) error {
|
||||
if len(cfg.ServerCAFile) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(errCertificateParse, err)
|
||||
}
|
||||
|
||||
err = checkIfCertificateSelfSigned(cert)
|
||||
if err != nil {
|
||||
return errors.Wrap(errAttVerification, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkIfCertificateSelfSigned(cert *x509.Certificate) error {
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AddCert(cert)
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: certPool,
|
||||
CurrentTime: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := cert.Verify(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !cgo
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func setupATLS(cfg AgentClientConfig) (credentials.TransportCredentials, error) {
|
||||
return nil, fmt.Errorf("aTLS is not supported without CGO. Please rebuild with CGO_ENABLED=1")
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
att "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
@@ -200,8 +201,9 @@ func TestClientSecure(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadAttestationPolicy(t *testing.T) {
|
||||
validJSON := `{"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
|
||||
validJSON := `{"pcr_values":{"sha256":{"0":"123"},"sha384":{"0":"123"}},"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
|
||||
invalidJSON := `{"invalid_json"`
|
||||
invalidJSONPCR := `{"pcr_values":{"sha256":{"0":true},"sha384":{"0":"123"}},"policy":{"report_data":"AAAA"},"root_of_trust":{"product_line":"Milan"}}`
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -219,19 +221,25 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
name: "Invalid JSON",
|
||||
manifestPath: "invalid_manifest.json",
|
||||
fileContent: invalidJSON,
|
||||
err: ErrAttestationPolicyDecode,
|
||||
err: att.ErrAttestationPolicyDecode,
|
||||
},
|
||||
{
|
||||
name: "Non-existent file",
|
||||
manifestPath: "nonexistent.json",
|
||||
fileContent: "",
|
||||
err: errAttestationPolicyOpen,
|
||||
err: att.ErrAttestationPolicyOpen,
|
||||
},
|
||||
{
|
||||
name: "Empty manifest path",
|
||||
manifestPath: "",
|
||||
fileContent: "",
|
||||
err: ErrAttestationPolicyMissing,
|
||||
err: att.ErrAttestationPolicyMissing,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON PCR",
|
||||
manifestPath: "invalid_manifest.json",
|
||||
fileContent: invalidJSONPCR,
|
||||
err: att.ErrAttestationPolicyDecode,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -243,13 +251,13 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
defer os.Remove(tt.manifestPath)
|
||||
}
|
||||
|
||||
config := check.Config{}
|
||||
err := ReadAttestationPolicy(tt.manifestPath, &config)
|
||||
config := att.Config{Config: &check.Config{}, PcrConfig: &att.PcrConfig{}}
|
||||
err := att.ReadAttestationPolicy(tt.manifestPath, &config)
|
||||
|
||||
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
|
||||
if tt.err == nil {
|
||||
assert.NotNil(t, config.Policy)
|
||||
assert.NotNil(t, config.RootOfTrust)
|
||||
assert.NotNil(t, config.Config.Policy)
|
||||
assert.NotNil(t, config.Config.RootOfTrust)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
|
||||
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type security int
|
||||
@@ -41,9 +35,6 @@ const (
|
||||
var (
|
||||
errGrpcConnect = errors.New("failed to connect to grpc server")
|
||||
errGrpcClose = errors.New("failed to close grpc connection")
|
||||
errAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
|
||||
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
|
||||
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
|
||||
errCertificateParse = errors.New("failed to parse x509 certificate")
|
||||
errAttVerification = errors.New("certificat is not sefl signed")
|
||||
errFailedToLoadClientCertKey = errors.New("failed to load client certificate and key")
|
||||
@@ -59,7 +50,7 @@ type BaseConfig struct {
|
||||
Timeout time.Duration `env:"TIMEOUT" envDefault:"60s"`
|
||||
ClientCert string `env:"CLIENT_CERT" envDefault:""`
|
||||
ClientKey string `env:"CLIENT_KEY" envDefault:""`
|
||||
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
|
||||
ServerCAFile string `env:"SERVER_CA_CERTS" envDefault:""`
|
||||
}
|
||||
|
||||
type AgentClientConfig struct {
|
||||
@@ -89,13 +80,8 @@ func (a CVMClientConfig) GetBaseConfig() BaseConfig {
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
// Close closes gRPC connection.
|
||||
Close() error
|
||||
|
||||
// Secure is used for pretty printing TLS info.
|
||||
Secure() string
|
||||
|
||||
// Connection returns the gRPC connection.
|
||||
Connection() *grpc.ClientConn
|
||||
}
|
||||
|
||||
@@ -124,7 +110,6 @@ func (c *client) Close() error {
|
||||
if err := c.ClientConn.Close(); err != nil {
|
||||
return errors.Wrap(errGrpcClose, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,8 +121,6 @@ func (c *client) Secure() string {
|
||||
return "with mTLS"
|
||||
case withaTLS:
|
||||
return WithATLS
|
||||
case withoutTLS:
|
||||
fallthrough
|
||||
default:
|
||||
return "without TLS"
|
||||
}
|
||||
@@ -147,25 +130,19 @@ func (c *client) Connection() *grpc.ClientConn {
|
||||
return c.ClientConn
|
||||
}
|
||||
|
||||
// connect creates new gRPC client and connect to gRPC server.
|
||||
func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
|
||||
}
|
||||
secure := withoutTLS
|
||||
var tc credentials.TransportCredentials
|
||||
|
||||
if agcfg, ok := cfg.(AgentClientConfig); ok && agcfg.AttestedTLS {
|
||||
err := ReadAttestationPolicy(agcfg.AttestationPolicy, "eprovider.AttConfigurationSEVSNP)
|
||||
tc, err := setupATLS(agcfg)
|
||||
if err != nil {
|
||||
return nil, secure, errors.Wrap(fmt.Errorf("failed to read Attestation Policy"), err)
|
||||
return nil, secure, err
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
VerifyPeerCertificate: verifyPeerCertificateATLS,
|
||||
}
|
||||
tc = credentials.NewTLS(tlsConfig)
|
||||
opts = append(opts, grpc.WithTransportCredentials(tc))
|
||||
opts = append(opts, grpc.WithContextDialer(CustomDialer))
|
||||
secure = withaTLS
|
||||
} else {
|
||||
@@ -174,13 +151,11 @@ func connect(cfg ClientConfiguration) (*grpc.ClientConn, security, error) {
|
||||
if err != nil {
|
||||
return nil, secure, err
|
||||
}
|
||||
tc = transportCreds
|
||||
opts = append(opts, grpc.WithTransportCredentials(transportCreds))
|
||||
secure = sec
|
||||
}
|
||||
|
||||
opts = append(opts, grpc.WithTransportCredentials(tc))
|
||||
|
||||
conn, err := grpc.NewClient(cfg.GetBaseConfig().URL, opts...)
|
||||
conn, err := grpc.Dial(cfg.GetBaseConfig().URL, opts...)
|
||||
if err != nil {
|
||||
return nil, secure, errors.Wrap(errGrpcConnect, err)
|
||||
}
|
||||
@@ -192,7 +167,6 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
|
||||
secure := withoutTLS
|
||||
tc := insecure.NewCredentials()
|
||||
|
||||
// Load Root CA certificates
|
||||
if serverCAFile != "" {
|
||||
rootCA, err := os.ReadFile(serverCAFile)
|
||||
if err != nil {
|
||||
@@ -209,7 +183,6 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
|
||||
}
|
||||
}
|
||||
|
||||
// Load mTLS certificates
|
||||
if clientCert != "" || clientKey != "" {
|
||||
certificate, err := tls.LoadX509KeyPair(clientCert, clientKey)
|
||||
if err != nil {
|
||||
@@ -222,69 +195,3 @@ func loadTLSConfig(serverCAFile, clientCert, clientKey string) (credentials.Tran
|
||||
|
||||
return tc, nil, secure
|
||||
}
|
||||
|
||||
func ReadAttestationPolicy(manifestPath string, attestationConfiguration *check.Config) error {
|
||||
if manifestPath != "" {
|
||||
manifest, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(errAttestationPolicyOpen, err)
|
||||
}
|
||||
|
||||
if err := protojson.Unmarshal(manifest, attestationConfiguration); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrAttestationPolicyMissing
|
||||
}
|
||||
|
||||
func CustomDialer(ctx context.Context, addr string) (net.Conn, error) {
|
||||
ip, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create a custom dialer")
|
||||
}
|
||||
|
||||
p, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad format of IP address: %v", err)
|
||||
}
|
||||
|
||||
conn, err := atls.DialTLSClient(ip, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create TLS connection")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func verifyPeerCertificateATLS(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
cert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(errCertificateParse, err)
|
||||
}
|
||||
|
||||
err = checkIfCertificateSelfSigned(cert)
|
||||
if err != nil {
|
||||
return errors.Wrap(errAttVerification, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkIfCertificateSelfSigned(cert *x509.Certificate) error {
|
||||
certPool := x509.NewCertPool()
|
||||
certPool.AddCert(cert)
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: certPool,
|
||||
CurrentTime: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := cert.Verify(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user