mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6169766666 | |||
| 5f339d2fab | |||
| 7e8eab77e7 | |||
| 9f31e2472b | |||
| e8e616ff62 | |||
| 0dce9d3083 | |||
| a37121dc7b | |||
| 1f0eccfae7 | |||
| 02aa7d7d85 | |||
| 27db9b29eb | |||
| 81fe0b11b5 | |||
| d5badba547 | |||
| c59a413765 | |||
| 3b9841a973 |
+3
-1
@@ -19,6 +19,7 @@ target/
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
!tools/nvidia-attestation-helper/Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
@@ -28,4 +29,5 @@ Cargo.lock
|
||||
|
||||
*.enc
|
||||
*.key
|
||||
*.pub
|
||||
*.pub
|
||||
.codex
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
BUILD_DIR = build
|
||||
SERVICES = manager agent cli attestation-service log-forwarder computation-runner egress-proxy ingress-proxy
|
||||
NVIDIA_ATTESTATION_HELPER = nvidia-attestation-helper
|
||||
NVIDIA_ATTESTATION_HELPER_DIR = tools/$(NVIDIA_ATTESTATION_HELPER)
|
||||
NVIDIA_ATTESTATION_HELPER_MANIFEST = $(NVIDIA_ATTESTATION_HELPER_DIR)/Cargo.toml
|
||||
NVIDIA_ATTESTATION_HELPER_BINARY = $(BUILD_DIR)/$(NVIDIA_ATTESTATION_HELPER)
|
||||
NVIDIA_ATTESTATION_HELPER_LIB_DIR = $(BUILD_DIR)/lib
|
||||
NVAT_SDK_CPP_DIR ?= $(firstword $(wildcard $(HOME)/.cargo/git/checkouts/attestation-sdk-*/*/nv-attestation-sdk-cpp))
|
||||
NVAT_SDK_CPP_BUILD_DIR ?= $(NVAT_SDK_CPP_DIR)/build
|
||||
NVAT_SDK_HEADER ?= $(NVAT_SDK_CPP_BUILD_DIR)/include/nvat.h
|
||||
NVAT_SDK_SHARED_LIB ?= $(NVAT_SDK_CPP_BUILD_DIR)/libnvat.so.1
|
||||
NVAT_SYSTEM_HEADER ?= /usr/include/nvat.h
|
||||
CARGO ?= cargo
|
||||
CMAKE ?= cmake
|
||||
CGO_ENABLED ?= 0
|
||||
GOARCH ?= amd64
|
||||
VERSION ?= $(shell git describe --abbrev=0 --tags --always)
|
||||
COMMIT ?= $(shell git rev-parse HEAD)
|
||||
TIME ?= $(shell date +%F_%T)
|
||||
EMBED_ENABLED ?= 0
|
||||
NVAT_USE_SYSTEM_LIB ?=
|
||||
INSTALL_DIR ?= /usr/local/bin
|
||||
CONFIG_DIR ?= /etc/cocos
|
||||
SERVICE_NAME ?= cocos-manager
|
||||
@@ -16,21 +29,53 @@ IGVM_BUILD_SCRIPT := ./scripts/igvmmeasure/igvm.sh
|
||||
define compile_service
|
||||
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
|
||||
go build -ldflags "-s -w \
|
||||
-X 'github.com/absmach/supermq.BuildTime=$(TIME)' \
|
||||
-X 'github.com/absmach/supermq.Version=$(VERSION)' \
|
||||
-X 'github.com/absmach/supermq.Commit=$(COMMIT)'" \
|
||||
-X 'github.com/absmach/magistrala.BuildTime=$(TIME)' \
|
||||
-X 'github.com/absmach/magistrala.Version=$(VERSION)' \
|
||||
-X 'github.com/absmach/magistrala.Commit=$(COMMIT)'" \
|
||||
$(if $(filter 1,$(EMBED_ENABLED)),-tags "embed",) \
|
||||
-o ${BUILD_DIR}/cocos-$(1) ./cmd/$(1)
|
||||
endef
|
||||
|
||||
.PHONY: all $(SERVICES) install clean
|
||||
NVIDIA_ATTESTATION_HELPER_CARGO_ENV = $(if $(filter 1,$(NVAT_USE_SYSTEM_LIB)),NVAT_USE_SYSTEM_LIB=1,)
|
||||
NVIDIA_ATTESTATION_HELPER_RUSTFLAGS = $(strip $(RUSTFLAGS) $(if $(filter 1,$(NVAT_USE_SYSTEM_LIB)),,-C link-arg=-Wl,-rpath,$$ORIGIN/lib))
|
||||
|
||||
.PHONY: all $(SERVICES) $(NVIDIA_ATTESTATION_HELPER) nvidia-attestation-helper-prereqs install clean
|
||||
|
||||
all: $(SERVICES)
|
||||
|
||||
$(SERVICES):
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $(BUILD_DIR)
|
||||
|
||||
$(SERVICES): | $(BUILD_DIR)
|
||||
$(call compile_service,$@)
|
||||
@if [ "$@" = "cli" ] || [ "$@" = "manager" ]; then $(MAKE) build-igvm; fi
|
||||
|
||||
nvidia-attestation-helper-prereqs:
|
||||
ifeq ($(filter 1,$(NVAT_USE_SYSTEM_LIB)),1)
|
||||
@test -f $(NVAT_SYSTEM_HEADER) || \
|
||||
( echo "Missing $(NVAT_SYSTEM_HEADER). Install the NVAT development package or run without NVAT_USE_SYSTEM_LIB=1."; exit 1 )
|
||||
@ldconfig -p | grep -q libnvat.so.1 || \
|
||||
( echo "libnvat.so.1 not found in the dynamic linker cache. Install the NVAT runtime package or run without NVAT_USE_SYSTEM_LIB=1."; exit 1 )
|
||||
else
|
||||
@if [ -z "$(NVAT_SDK_CPP_DIR)" ]; then \
|
||||
echo "Unable to locate nv-attestation-sdk-cpp under $$HOME/.cargo/git/checkouts."; \
|
||||
echo "Run 'cargo fetch --manifest-path $(NVIDIA_ATTESTATION_HELPER_MANIFEST)' first, or install NVAT and use 'make NVAT_USE_SYSTEM_LIB=1 $(NVIDIA_ATTESTATION_HELPER)'."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f "$(NVAT_SDK_HEADER)" ] || [ ! -f "$(NVAT_SDK_SHARED_LIB)" ]; then \
|
||||
$(CMAKE) -S $(NVAT_SDK_CPP_DIR) -B $(NVAT_SDK_CPP_BUILD_DIR) && \
|
||||
$(CMAKE) --build $(NVAT_SDK_CPP_BUILD_DIR); \
|
||||
fi
|
||||
endif
|
||||
|
||||
$(NVIDIA_ATTESTATION_HELPER): nvidia-attestation-helper-prereqs | $(BUILD_DIR)
|
||||
RUSTFLAGS='$(NVIDIA_ATTESTATION_HELPER_RUSTFLAGS)' $(NVIDIA_ATTESTATION_HELPER_CARGO_ENV) $(CARGO) build --manifest-path $(NVIDIA_ATTESTATION_HELPER_MANIFEST) --release
|
||||
install -m 755 $(NVIDIA_ATTESTATION_HELPER_DIR)/target/release/$(NVIDIA_ATTESTATION_HELPER) $(NVIDIA_ATTESTATION_HELPER_BINARY)
|
||||
@if [ "$(filter 1,$(NVAT_USE_SYSTEM_LIB))" != "1" ]; then \
|
||||
install -d $(NVIDIA_ATTESTATION_HELPER_LIB_DIR); \
|
||||
install -m 755 $(NVAT_SDK_SHARED_LIB) $(NVIDIA_ATTESTATION_HELPER_LIB_DIR)/libnvat.so.1; \
|
||||
fi
|
||||
|
||||
protoc:
|
||||
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/agent.proto
|
||||
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative manager/manager.proto
|
||||
|
||||
@@ -21,12 +21,20 @@ The service is configured using the environment variables from the following tab
|
||||
| AGENT_CVM_ID | Unique identifier for the CVM (Confidential Virtual Machine) | "" |
|
||||
| AGENT_CERTS_TOKEN | Authentication token for certificate service access | "" |
|
||||
| AGENT_MAA_URL | Microsoft Azure Attestation service URL for Azure attestation | https://sharedeus2.eus2.attest.azure.net |
|
||||
| AZURE_TDX_IMDS_URL | Azure TDX quote endpoint used by direct Azure TDX attestation | http://169.254.169.254/acc/tdquote |
|
||||
| AZURE_HCL_REFRESH_WAIT | Wait after writing TDX report data to Azure HCL vTPM storage before reading the refreshed HCL report | 3s |
|
||||
| AGENT_OS_BUILD | Operating system build information for attestation | UVC |
|
||||
| AGENT_OS_DISTRO | Operating system distribution information for attestation | UVC |
|
||||
| AGENT_OS_TYPE | Operating system type information for attestation | UVC |
|
||||
| ATTESTATION_SERVICE_SOCKET | Unix socket path for attestation service communication | /run/cocos/attestation.sock |
|
||||
| AGENT_ENABLE_ATLS | Enable Attestation TLS for secure communication | true |
|
||||
|
||||
### Azure TDX Attestation
|
||||
|
||||
When the agent runs on an Azure TDX CVM, Azure attestation uses the direct Azure TDX flow. The agent writes TDX report data to Azure HCL vTPM storage, reads the refreshed HCL report, requests a TD quote from Azure IMDS, and submits the quote plus HCL runtime data to Microsoft Azure Attestation. This path does not depend on Confidential Containers attestation-agent `GetEvidence` or KBS token retrieval.
|
||||
|
||||
`AGENT_MAA_URL` selects the Microsoft Azure Attestation endpoint. `AZURE_TDX_IMDS_URL` can override the Azure IMDS TDX quote endpoint, and `AZURE_HCL_REFRESH_WAIT` controls the wait used to avoid reading a stale HCL report after report-data is written.
|
||||
|
||||
### Remote Resource Download (Optional)
|
||||
|
||||
The agent supports downloading encrypted algorithms and datasets from remote registries (S3, HTTP/HTTPS) and retrieving decryption keys from a Key Broker Service (KBS) via attestation.
|
||||
|
||||
@@ -5,24 +5,25 @@ This guide explains how to test Cocos with encrypted remote resources using the
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CVM (Agent) │
|
||||
│ │
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ CVM (Agent) │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Agent │───▶│ Skopeo │───▶│ CoCo Keyprovider│ │
|
||||
│ └──────────┘ │ (ocicrypt) │ │ (gRPC:50011) │ │
|
||||
│ └────────────────┘ └────────┬────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼────────┐ │
|
||||
│ │ Attestation │ │
|
||||
│ │ Agent (50002) │ │
|
||||
│ └────────┬────────┘ │
|
||||
└──────────────────────────────────────────────────┼──────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ KBS Server │
|
||||
│ (Host:8080) │
|
||||
└─────────────────┘
|
||||
│ │ Agent │──▶│ Skopeo │──▶│ CoCo Keyprovider│ │
|
||||
│ │ │ │ (ocicrypt) │ │ (gRPC:50011) │ │
|
||||
│ │ │ └───────┬────────┘ └────────┬────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ┌───────▼────────┐ ┌────────▼────────┐ │
|
||||
│ │ │──▶│ S3/HTTP │ │ Attestation │ │
|
||||
│ │ │ │ Downloader │ │ Agent (50002) │ │
|
||||
│ └────┬─────┘ └───────┬────────┘ └────────┬────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┼──────────────────────┘ │
|
||||
└────────┬─────────────────┼──────────────────────┬──────────┘
|
||||
│ (Resource) │ (Resource) │ (Attest)
|
||||
▼ ▼ ▼
|
||||
OCI Registry S3 / HTTP / GCS KBS
|
||||
(Key Broker)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
@@ -280,17 +281,22 @@ go build -o build/cvms-test ./test/cvms/main.go
|
||||
HOST=$HOST_IP PORT=7001 ./build/cvms-test \
|
||||
-public-key-path ./public.pem \
|
||||
-attested-tls-bool false \
|
||||
-kbs-url http://$HOST_IP:8080 \
|
||||
-algo-type python \
|
||||
-algo-source-url docker://$HOST_IP:5000/encrypted-lin-reg:v1.0 \
|
||||
-algo-kbs-path default/key/algo-key \
|
||||
-algo-kbs-url http://$HOST_IP:8080 \
|
||||
-algo-hash $ALGO_HASH \
|
||||
-algo-args datasets/dataset_0.csv \
|
||||
-dataset-source-urls docker://$HOST_IP:5000/encrypted-iris:v1.0 \
|
||||
-dataset-kbs-paths default/key/dataset-key \
|
||||
-dataset-kbs-urls http://$HOST_IP:8080 \
|
||||
-dataset-hash $DATASET_HASH
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You must specify the KBS URL for each encrypted resource using `-algo-kbs-url` and `-dataset-kbs-urls`. A global KBS is no longer supported.
|
||||
|
||||
|
||||
### 3. Create VM via CLI (Host)
|
||||
|
||||
```bash
|
||||
@@ -356,17 +362,31 @@ The CVMS server sends this manifest to the agent:
|
||||
"type": "oci-image",
|
||||
"uri": "docker://localhost:5000/encrypted-lin-reg:v1.0",
|
||||
"encrypted": true,
|
||||
"kbs_resource_path": "default/key/algo-key"
|
||||
"kbs_resource_path": "default/key/algo-key",
|
||||
"kbs": {
|
||||
"url": "http://192.168.100.15:8080",
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"datasets": [
|
||||
{
|
||||
"type": "oci-image",
|
||||
"uri": "docker://localhost:5000/encrypted-iris:v1.0",
|
||||
"encrypted": true,
|
||||
"kbs_resource_path": "default/key/dataset-key"
|
||||
"filename": "iris.csv",
|
||||
"source": {
|
||||
"type": "oci-image",
|
||||
"url": "docker://localhost:5000/encrypted-iris:v1.0",
|
||||
"encrypted": true,
|
||||
"kbs_resource_path": "default/key/dataset-key"
|
||||
},
|
||||
"kbs": {
|
||||
"url": "http://192.168.100.20:8080",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"kbs_url": "http://192.168.100.15:8080"
|
||||
"kbs": {
|
||||
"url": "http://192.168.100.15:8080",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -406,27 +426,43 @@ curl http://HOST_IP:8080/kbs/v0/auth
|
||||
# Ensure KBS is configured for sample attestation
|
||||
```
|
||||
|
||||
## Differences from Previous Approach
|
||||
## 4. Testing with Non-OCI Sources (S3, HTTP, GCS)
|
||||
|
||||
| Aspect | Old (Custom) | New (CoCo Standard) |
|
||||
|--------|-------------|---------------------|
|
||||
| **Download** | Custom S3/HTTP clients | Skopeo (OCI standard) |
|
||||
| **Decryption** | Custom KBS client | CoCo Keyprovider |
|
||||
| **Attestation** | Direct KBS RCAR | AA → CoCo KP → KBS |
|
||||
| **Format** | Raw encrypted files | OCI encrypted images |
|
||||
| **Complexity** | ~2000 lines custom code | Standard CoCo components |
|
||||
The `cvms` test utility also supports testing remote encrypted resources hosted in more traditional environments like S3-compatible storage or simple web servers, bypassing the need for container registries and OCI images.
|
||||
|
||||
## Benefits
|
||||
### Supported Flags
|
||||
|
||||
1. **Standards Compliance**: Uses OCI and CoCo standards
|
||||
2. **Better Tooling**: Leverage Skopeo, Docker, Podman ecosystem
|
||||
3. **Simplified Code**: Remove custom registry/decryption logic
|
||||
4. **Proven Solution**: Battle-tested CoCo components
|
||||
5. **Docker Native**: Works with existing Docker workflows
|
||||
The following flags define how resources should be fetched:
|
||||
|
||||
## Next Steps
|
||||
- `--algo-source-url`: The URL of the algorithm (e.g. `s3://bucket/algo.bin`, `https://server/algo.bin`)
|
||||
- `--algo-source-type`: The type of remote endpoint (`s3`, `gcs`, `https`, `http`). If omitted, it will automatically be inferred from the URL scheme.
|
||||
- `--algo-kbs-path`: The KBS path to retrieve the AES-256-GCM key from. If present, the agent will attempt decryption.
|
||||
- `--dataset-source-urls` and `--dataset-source-type`: Defines the locations and protocols for datasets.
|
||||
|
||||
- Encrypt your algorithms and datasets as OCI images
|
||||
- Push to your preferred OCI registry (Docker Hub, GHCR, etc.)
|
||||
- Update computation manifests to use `oci-image` type
|
||||
- Test end-to-end flow with encrypted workloads
|
||||
### Encryption Format for Non-OCI Sources
|
||||
|
||||
Unlike OCI images where `ocicrypt` wraps the dataset, resources hosted on HTTP/S3 must be straightforwardly encrypted using **AES-256-GCM**.
|
||||
|
||||
The expected format is exactly as produced by standard Go AES-GCM:
|
||||
`nonce (12 bytes) || ciphertext || tag`
|
||||
|
||||
### Test Example
|
||||
|
||||
If you had a Python script encrypted using a key hosted at KBS path `default/my-keys/python-script` and uploaded to `s3://my-secure-bucket/script.enc`, you could run:
|
||||
|
||||
```bash
|
||||
cd test
|
||||
go run cvms/main.go --algo-source-url="s3://my-secure-bucket/script.enc" \
|
||||
--algo-source-type="s3" \
|
||||
--algo-kbs-path="default/my-keys/python-script" \
|
||||
--algo-type="python" \
|
||||
--public-key-path=./test-data/public-key.pem
|
||||
```
|
||||
|
||||
The system will:
|
||||
1. Connect via `attestation-agent` to the KBS to retrieve the symmetric key
|
||||
2. Use Google Cloud Storage client library methods (support for generic S3 via environment variables is standard) to fetch the resource
|
||||
3. Decrypt using AES-256-GCM
|
||||
4. Run the code normally
|
||||
|
||||
---
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent/events/mocks"
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm/logging"
|
||||
"github.com/ultravioletrs/cocos/agent/events/mocks"
|
||||
@@ -88,6 +89,7 @@ func TestRun(t *testing.T) {
|
||||
}
|
||||
|
||||
eventsSvc := new(mocks.Service)
|
||||
eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
@@ -129,6 +131,7 @@ func TestRunWithRequirements(t *testing.T) {
|
||||
}
|
||||
|
||||
eventsSvc := new(mocks.Service)
|
||||
eventsSvc.EXPECT().SendEvent(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return().Maybe()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ func (req azureAttestationTokenReq) validate() error {
|
||||
|
||||
func validateAttestationType(attType attestation.PlatformType) error {
|
||||
switch attType {
|
||||
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, attestation.TDX:
|
||||
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, attestation.Azure, attestation.TDX:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid attestation type")
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
@@ -44,7 +44,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
manifest := agent.Computation{
|
||||
ResultConsumers: []agent.ResultConsumer{{UserKey: resultConsumerPubKey}},
|
||||
Datasets: []agent.Dataset{{UserKey: dataProviderPubKey}},
|
||||
Algorithm: agent.Algorithm{UserKey: algorithmProviderPubKey},
|
||||
Algorithm: &agent.Algorithm{UserKey: algorithmProviderPubKey},
|
||||
}
|
||||
|
||||
auth, err := New(manifest)
|
||||
|
||||
+12
-4
@@ -22,9 +22,16 @@ type AgentConfig struct {
|
||||
|
||||
// ResourceSource specifies the location of a remote encrypted resource.
|
||||
type ResourceSource struct {
|
||||
// Type is the type of resource source (currently only "oci-image" is supported)
|
||||
// Type is the type of resource source.
|
||||
// Supported values: "oci-image", "s3", "gcs", "https", "http"
|
||||
Type string `json:"type,omitempty"`
|
||||
// URL is the location of the resource (e.g., docker://registry/repo:tag)
|
||||
// URL is the location of the resource.
|
||||
// Examples:
|
||||
// - OCI: "docker://registry/repo:tag"
|
||||
// - S3: "s3://bucket/key"
|
||||
// - GCS: "gs://bucket/key"
|
||||
// - HTTPS: "https://host/path/to/file"
|
||||
// - HTTP: "http://host/path/to/file"
|
||||
URL string `json:"url,omitempty"`
|
||||
// KBSResourcePath is the path to the decryption key in KBS (e.g., "default/key/my-key")
|
||||
KBSResourcePath string `json:"kbs_resource_path,omitempty"`
|
||||
@@ -45,9 +52,8 @@ type Computation struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Datasets Datasets `json:"datasets,omitempty"`
|
||||
Algorithm Algorithm `json:"algorithm,omitempty"`
|
||||
Algorithm *Algorithm `json:"algorithm,omitempty"`
|
||||
ResultConsumers []ResultConsumer `json:"result_consumers,omitempty"`
|
||||
KBS KBSConfig `json:"kbs,omitempty"`
|
||||
}
|
||||
|
||||
type ResultConsumer struct {
|
||||
@@ -69,6 +75,7 @@ type Dataset struct {
|
||||
Filename string `json:"filename,omitempty"`
|
||||
Source *ResourceSource `json:"source,omitempty"` // Optional remote source
|
||||
Decompress bool `json:"decompress,omitempty"`
|
||||
KBS *KBSConfig `json:"kbs,omitempty"`
|
||||
}
|
||||
|
||||
type Datasets []Dataset
|
||||
@@ -81,6 +88,7 @@ type Algorithm struct {
|
||||
Source *ResourceSource `json:"source,omitempty"` // Optional remote source
|
||||
AlgoType string `json:"algo_type,omitempty"`
|
||||
AlgoArgs []string `json:"algo_args,omitempty"`
|
||||
KBS *KBSConfig `json:"kbs,omitempty"`
|
||||
}
|
||||
|
||||
type ManifestIndexKey struct{}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"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"
|
||||
@@ -234,9 +234,10 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
|
||||
}
|
||||
|
||||
if runReq.Algorithm != nil {
|
||||
ac.Algorithm = agent.Algorithm{
|
||||
Hash: [32]byte(runReq.Algorithm.Hash),
|
||||
UserKey: runReq.Algorithm.UserKey,
|
||||
ac.Algorithm = &agent.Algorithm{
|
||||
Hash: [32]byte(runReq.Algorithm.Hash),
|
||||
UserKey: runReq.Algorithm.UserKey,
|
||||
AlgoType: runReq.Algorithm.AlgoType,
|
||||
}
|
||||
// Copy remote source if configured
|
||||
if runReq.Algorithm.Source != nil {
|
||||
@@ -246,8 +247,13 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
|
||||
Encrypted: runReq.Algorithm.Source.Encrypted,
|
||||
}
|
||||
}
|
||||
ac.Algorithm.AlgoType = runReq.Algorithm.AlgoType
|
||||
ac.Algorithm.AlgoArgs = runReq.Algorithm.AlgoArgs
|
||||
if runReq.Algorithm.Kbs != nil {
|
||||
ac.Algorithm.KBS = &agent.KBSConfig{
|
||||
URL: runReq.Algorithm.Kbs.Url,
|
||||
Enabled: runReq.Algorithm.Kbs.Enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ds := range runReq.Datasets {
|
||||
@@ -265,6 +271,12 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
|
||||
}
|
||||
}
|
||||
dataset.Decompress = ds.Decompress
|
||||
if ds.Kbs != nil {
|
||||
dataset.KBS = &agent.KBSConfig{
|
||||
URL: ds.Kbs.Url,
|
||||
Enabled: ds.Kbs.Enabled,
|
||||
}
|
||||
}
|
||||
ac.Datasets = append(ac.Datasets, dataset)
|
||||
}
|
||||
|
||||
@@ -274,14 +286,6 @@ func (client *CVMSClient) executeRun(ctx context.Context, runReq *cvms.Computati
|
||||
})
|
||||
}
|
||||
|
||||
// Copy KBS configuration
|
||||
if runReq.Kbs != nil {
|
||||
ac.KBS = agent.KBSConfig{
|
||||
URL: runReq.Kbs.Url,
|
||||
Enabled: runReq.Kbs.Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the agent is in the correct state to initialize a new computation.
|
||||
// If the agent is already processing this computation (e.g., after a reconnection),
|
||||
// skip initialization to avoid state errors.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
@@ -554,11 +554,12 @@ func TestManagerClient_handleRunReqChunksWithRemoteSource(t *testing.T) {
|
||||
KbsResourcePath: "default/key/algo-key",
|
||||
Encrypted: true,
|
||||
},
|
||||
Kbs: &cvms.KBSConfig{
|
||||
Url: "https://kbs.example.com:8080",
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
Kbs: &cvms.KBSConfig{
|
||||
Url: "https://kbs.example.com:8080",
|
||||
Enabled: true,
|
||||
},
|
||||
|
||||
ResultConsumers: []*cvms.ResultConsumer{
|
||||
{
|
||||
UserKey: []byte("test-consumer"),
|
||||
@@ -577,8 +578,8 @@ func TestManagerClient_handleRunReqChunksWithRemoteSource(t *testing.T) {
|
||||
|
||||
mockSvc.On("State").Return("ReceivingManifest")
|
||||
mockSvc.On("InitComputation", mock.Anything, mock.MatchedBy(func(c agent.Computation) bool {
|
||||
// Verify KBS config is passed
|
||||
if !c.KBS.Enabled || c.KBS.URL != "https://kbs.example.com:8080" {
|
||||
// Verify Algorithm KBS config is passed
|
||||
if c.Algorithm.KBS == nil || !c.Algorithm.KBS.Enabled || c.Algorithm.KBS.URL != "https://kbs.example.com:8080" {
|
||||
return false
|
||||
}
|
||||
// Verify algorithm source is passed
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
|
||||
+33
-23
@@ -826,7 +826,6 @@ type ComputationRunReq struct {
|
||||
Algorithm *Algorithm `protobuf:"bytes,5,opt,name=algorithm,proto3" json:"algorithm,omitempty"`
|
||||
ResultConsumers []*ResultConsumer `protobuf:"bytes,6,rep,name=result_consumers,json=resultConsumers,proto3" json:"result_consumers,omitempty"`
|
||||
AgentConfig *AgentConfig `protobuf:"bytes,7,opt,name=agent_config,json=agentConfig,proto3" json:"agent_config,omitempty"`
|
||||
Kbs *KBSConfig `protobuf:"bytes,8,opt,name=kbs,proto3" json:"kbs,omitempty"` // Optional KBS configuration for remote resources
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -910,13 +909,6 @@ func (x *ComputationRunReq) GetAgentConfig() *AgentConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComputationRunReq) GetKbs() *KBSConfig {
|
||||
if x != nil {
|
||||
return x.Kbs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ResultConsumer struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserKey []byte `protobuf:"bytes,1,opt,name=userKey,proto3" json:"userKey,omitempty"`
|
||||
@@ -968,6 +960,7 @@ type Dataset struct {
|
||||
Filename string `protobuf:"bytes,3,opt,name=filename,proto3" json:"filename,omitempty"`
|
||||
Source *Source `protobuf:"bytes,4,opt,name=source,proto3" json:"source,omitempty"` // Optional remote source for encrypted dataset
|
||||
Decompress bool `protobuf:"varint,5,opt,name=decompress,proto3" json:"decompress,omitempty"`
|
||||
Kbs *KBSConfig `protobuf:"bytes,6,opt,name=kbs,proto3" json:"kbs,omitempty"` // Optional KBS configuration override
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -1037,6 +1030,13 @@ func (x *Dataset) GetDecompress() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Dataset) GetKbs() *KBSConfig {
|
||||
if x != nil {
|
||||
return x.Kbs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Algorithm struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // should be sha3.Sum256, 32 byte length.
|
||||
@@ -1044,6 +1044,7 @@ type Algorithm struct {
|
||||
Source *Source `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"` // Optional remote source for encrypted algorithm
|
||||
AlgoType string `protobuf:"bytes,4,opt,name=algo_type,json=algoType,proto3" json:"algo_type,omitempty"`
|
||||
AlgoArgs []string `protobuf:"bytes,5,rep,name=algo_args,json=algoArgs,proto3" json:"algo_args,omitempty"`
|
||||
Kbs *KBSConfig `protobuf:"bytes,6,opt,name=kbs,proto3" json:"kbs,omitempty"` // Optional KBS configuration override
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -1113,6 +1114,13 @@ func (x *Algorithm) GetAlgoArgs() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Algorithm) GetKbs() *KBSConfig {
|
||||
if x != nil {
|
||||
return x.Kbs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Type of source: "oci-image" (only OCI images supported for CoCo)
|
||||
@@ -1485,7 +1493,7 @@ const file_agent_cvms_cvms_proto_rawDesc = "" +
|
||||
"\fRunReqChunks\x12\x12\n" +
|
||||
"\x04data\x18\x01 \x01(\fR\x04data\x12\x0e\n" +
|
||||
"\x02id\x18\x02 \x01(\tR\x02id\x12\x17\n" +
|
||||
"\ais_last\x18\x03 \x01(\bR\x06isLast\"\xcd\x02\n" +
|
||||
"\ais_last\x18\x03 \x01(\bR\x06isLast\"\xaa\x02\n" +
|
||||
"\x11ComputationRunReq\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
|
||||
@@ -1493,10 +1501,9 @@ const file_agent_cvms_cvms_proto_rawDesc = "" +
|
||||
"\bdatasets\x18\x04 \x03(\v2\r.cvms.DatasetR\bdatasets\x12-\n" +
|
||||
"\talgorithm\x18\x05 \x01(\v2\x0f.cvms.AlgorithmR\talgorithm\x12?\n" +
|
||||
"\x10result_consumers\x18\x06 \x03(\v2\x14.cvms.ResultConsumerR\x0fresultConsumers\x124\n" +
|
||||
"\fagent_config\x18\a \x01(\v2\x11.cvms.AgentConfigR\vagentConfig\x12!\n" +
|
||||
"\x03kbs\x18\b \x01(\v2\x0f.cvms.KBSConfigR\x03kbs\"*\n" +
|
||||
"\fagent_config\x18\a \x01(\v2\x11.cvms.AgentConfigR\vagentConfig\"*\n" +
|
||||
"\x0eResultConsumer\x12\x18\n" +
|
||||
"\auserKey\x18\x01 \x01(\fR\auserKey\"\x99\x01\n" +
|
||||
"\auserKey\x18\x01 \x01(\fR\auserKey\"\xbc\x01\n" +
|
||||
"\aDataset\x12\x12\n" +
|
||||
"\x04hash\x18\x01 \x01(\fR\x04hash\x12\x18\n" +
|
||||
"\auserKey\x18\x02 \x01(\fR\auserKey\x12\x1a\n" +
|
||||
@@ -1504,13 +1511,15 @@ const file_agent_cvms_cvms_proto_rawDesc = "" +
|
||||
"\x06source\x18\x04 \x01(\v2\f.cvms.SourceR\x06source\x12\x1e\n" +
|
||||
"\n" +
|
||||
"decompress\x18\x05 \x01(\bR\n" +
|
||||
"decompress\"\x99\x01\n" +
|
||||
"decompress\x12!\n" +
|
||||
"\x03kbs\x18\x06 \x01(\v2\x0f.cvms.KBSConfigR\x03kbs\"\xbc\x01\n" +
|
||||
"\tAlgorithm\x12\x12\n" +
|
||||
"\x04hash\x18\x01 \x01(\fR\x04hash\x12\x18\n" +
|
||||
"\auserKey\x18\x02 \x01(\fR\auserKey\x12$\n" +
|
||||
"\x06source\x18\x03 \x01(\v2\f.cvms.SourceR\x06source\x12\x1b\n" +
|
||||
"\talgo_type\x18\x04 \x01(\tR\balgoType\x12\x1b\n" +
|
||||
"\talgo_args\x18\x05 \x03(\tR\balgoArgs\"x\n" +
|
||||
"\talgo_args\x18\x05 \x03(\tR\balgoArgs\x12!\n" +
|
||||
"\x03kbs\x18\x06 \x01(\v2\x0f.cvms.KBSConfigR\x03kbs\"x\n" +
|
||||
"\x06Source\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x10\n" +
|
||||
"\x03url\x18\x02 \x01(\tR\x03url\x12*\n" +
|
||||
@@ -1591,16 +1600,17 @@ var file_agent_cvms_cvms_proto_depIdxs = []int32{
|
||||
14, // 15: cvms.ComputationRunReq.algorithm:type_name -> cvms.Algorithm
|
||||
12, // 16: cvms.ComputationRunReq.result_consumers:type_name -> cvms.ResultConsumer
|
||||
17, // 17: cvms.ComputationRunReq.agent_config:type_name -> cvms.AgentConfig
|
||||
16, // 18: cvms.ComputationRunReq.kbs:type_name -> cvms.KBSConfig
|
||||
15, // 19: cvms.Dataset.source:type_name -> cvms.Source
|
||||
15, // 18: cvms.Dataset.source:type_name -> cvms.Source
|
||||
16, // 19: cvms.Dataset.kbs:type_name -> cvms.KBSConfig
|
||||
15, // 20: cvms.Algorithm.source:type_name -> cvms.Source
|
||||
7, // 21: cvms.Service.Process:input_type -> cvms.ClientStreamMessage
|
||||
8, // 22: cvms.Service.Process:output_type -> cvms.ServerStreamMessage
|
||||
22, // [22:23] is the sub-list for method output_type
|
||||
21, // [21:22] is the sub-list for method input_type
|
||||
21, // [21:21] is the sub-list for extension type_name
|
||||
21, // [21:21] is the sub-list for extension extendee
|
||||
0, // [0:21] is the sub-list for field type_name
|
||||
16, // 21: cvms.Algorithm.kbs:type_name -> cvms.KBSConfig
|
||||
7, // 22: cvms.Service.Process:input_type -> cvms.ClientStreamMessage
|
||||
8, // 23: cvms.Service.Process:output_type -> cvms.ServerStreamMessage
|
||||
23, // [23:24] is the sub-list for method output_type
|
||||
22, // [22:23] is the sub-list for method input_type
|
||||
22, // [22:22] is the sub-list for extension type_name
|
||||
22, // [22:22] is the sub-list for extension extendee
|
||||
0, // [0:22] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_agent_cvms_cvms_proto_init() }
|
||||
|
||||
@@ -92,7 +92,6 @@ message ComputationRunReq {
|
||||
Algorithm algorithm = 5;
|
||||
repeated ResultConsumer result_consumers = 6;
|
||||
AgentConfig agent_config = 7;
|
||||
KBSConfig kbs = 8; // Optional KBS configuration for remote resources
|
||||
}
|
||||
|
||||
message ResultConsumer {
|
||||
@@ -105,6 +104,7 @@ message Dataset {
|
||||
string filename = 3;
|
||||
Source source = 4; // Optional remote source for encrypted dataset
|
||||
bool decompress = 5;
|
||||
KBSConfig kbs = 6; // Optional KBS configuration override
|
||||
}
|
||||
|
||||
message Algorithm {
|
||||
@@ -113,11 +113,12 @@ message Algorithm {
|
||||
Source source = 3; // Optional remote source for encrypted algorithm
|
||||
string algo_type = 4;
|
||||
repeated string algo_args = 5;
|
||||
KBSConfig kbs = 6; // Optional KBS configuration override
|
||||
}
|
||||
|
||||
message Source {
|
||||
string type = 1; // Type of source: "oci-image" (only OCI images supported for CoCo)
|
||||
string url = 2; // URL of the OCI image (e.g., docker://registry/repo:tag)
|
||||
string type = 1; // Type of source: "oci-image", "s3", "gcs", "https", "http"
|
||||
string url = 2; // URL of the resource (e.g., docker://registry/repo:tag, s3://bucket/key, https://host/path)
|
||||
string kbs_resource_path = 3; // Path to decryption key in KBS (e.g., "default/key/my-key")
|
||||
bool encrypted = 4; // Whether the resource is encrypted (requires KBS)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestAgentServer_Start(t *testing.T) {
|
||||
ID: "test-computation-1",
|
||||
Name: "Test Computation",
|
||||
Description: "A test computation",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x01, 0x02, 0x03},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -140,7 +140,7 @@ func TestAgentServer_Start(t *testing.T) {
|
||||
ID: "test-computation-2",
|
||||
Name: "Test Computation 2",
|
||||
Description: "Another test computation",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x07, 0x08, 0x09},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -168,7 +168,7 @@ func TestAgentServer_Start(t *testing.T) {
|
||||
cmp: agent.Computation{
|
||||
ID: "test-computation-3",
|
||||
Name: "Minimal Test",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x0d, 0x0e, 0x0f},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -244,7 +244,7 @@ func TestAgentServer_Stop(t *testing.T) {
|
||||
cmp := agent.Computation{
|
||||
ID: "test-stop-computation",
|
||||
Name: "Stop Test",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x19, 0x1a, 0x1b},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -303,7 +303,7 @@ func TestAgentServer_StopMultipleTimes(t *testing.T) {
|
||||
cmp := agent.Computation{
|
||||
ID: "test-multiple-stop",
|
||||
Name: "Multiple Stop Test",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x1f, 0x20, 0x21},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -346,7 +346,7 @@ func TestAgentServer_StartAfterStop(t *testing.T) {
|
||||
cmp := agent.Computation{
|
||||
ID: "test-restart",
|
||||
Name: "Restart Test",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x25, 0x26, 0x27},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -377,7 +377,7 @@ func TestAgentServer_StartAfterStop(t *testing.T) {
|
||||
cmp2 := agent.Computation{
|
||||
ID: "test-restart-2",
|
||||
Name: "Restart Test 2",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x2b, 0x2c, 0x2d},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -426,7 +426,7 @@ func TestAgentServer_ConfigValidation(t *testing.T) {
|
||||
cmp: agent.Computation{
|
||||
ID: "valid-config-test",
|
||||
Name: "Valid Config Test",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x31, 0x32, 0x33},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -450,7 +450,7 @@ func TestAgentServer_ConfigValidation(t *testing.T) {
|
||||
cmp: agent.Computation{
|
||||
ID: "minimal-config-test",
|
||||
Name: "Minimal Config Test",
|
||||
Algorithm: agent.Algorithm{
|
||||
Algorithm: &agent.Algorithm{
|
||||
Hash: [32]byte{0x37, 0x38, 0x39},
|
||||
UserKey: pubKey,
|
||||
},
|
||||
@@ -474,7 +474,7 @@ func TestAgentServer_ConfigValidation(t *testing.T) {
|
||||
cmp: agent.Computation{
|
||||
ID: "default-port-test",
|
||||
Name: "Default Port Test",
|
||||
Algorithm: agent.Algorithm{Hash: [32]byte{0x3d, 0x3e, 0x3f}, UserKey: pubKey},
|
||||
Algorithm: &agent.Algorithm{Hash: [32]byte{0x3d, 0x3e, 0x3f}, UserKey: pubKey},
|
||||
Datasets: []agent.Dataset{
|
||||
{Hash: [32]byte{0x40, 0x41, 0x42}, UserKey: pubKey},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/pkg/resource"
|
||||
)
|
||||
|
||||
type MockDownloader struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockDownloader) Download(ctx context.Context, url string, destPath string) error {
|
||||
args := m.Called(ctx, url, destPath)
|
||||
if args.Error(0) == nil {
|
||||
// Simulate writing to destPath if it's a success
|
||||
content := "mock content"
|
||||
if len(args) > 1 {
|
||||
if c, ok := args.Get(1).(string); ok {
|
||||
content = c
|
||||
}
|
||||
}
|
||||
_ = os.MkdirAll(filepath.Dir(destPath), 0o755)
|
||||
_ = os.WriteFile(destPath, []byte(content), 0o644)
|
||||
}
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockDownloader) Type() string {
|
||||
return m.Called().String(0)
|
||||
}
|
||||
|
||||
func TestDownloadAndDecryptGenericResource(t *testing.T) {
|
||||
registry := resource.NewRegistry()
|
||||
mockDownloader := new(MockDownloader)
|
||||
mockDownloader.On("Type").Return(resource.SourceTypeHTTP)
|
||||
registry.Register(mockDownloader)
|
||||
|
||||
svc := &agentService{
|
||||
logger: slog.Default(),
|
||||
resourceRegistry: registry,
|
||||
computation: Computation{
|
||||
Algorithm: &Algorithm{
|
||||
KBS: &KBSConfig{
|
||||
Enabled: true,
|
||||
URL: "http://mock-kbs",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("Successful download without encryption", func(t *testing.T) {
|
||||
source := &ResourceSource{
|
||||
URL: "http://example.com/resource",
|
||||
}
|
||||
destPath := filepath.Join(os.TempDir(), "cocos-resources", "algo", "resource")
|
||||
mockDownloader.On("Download", ctx, source.URL, destPath).Return(nil, "some data").Once()
|
||||
|
||||
res, err := svc.downloadAndDecryptGenericResource(ctx, source, resource.SourceTypeHTTP, "", "algo")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte("some data"), res.Data)
|
||||
mockDownloader.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Successful download with encryption", func(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
_, _ = io.ReadFull(rand.Reader, key)
|
||||
|
||||
plaintext := []byte("secret data")
|
||||
block, _ := aes.NewCipher(key)
|
||||
gcm, _ := cipher.NewGCM(block)
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
_, _ = io.ReadFull(rand.Reader, nonce)
|
||||
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
|
||||
// Mock KBS
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(key)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
svc.computation.Algorithm.KBS.URL = ts.URL
|
||||
|
||||
source := &ResourceSource{
|
||||
URL: "http://example.com/encrypted",
|
||||
Encrypted: true,
|
||||
KBSResourcePath: "keys/1",
|
||||
}
|
||||
destPath := filepath.Join(os.TempDir(), "cocos-resources", "data", "encrypted")
|
||||
mockDownloader.On("Download", ctx, source.URL, destPath).Return(nil, string(ciphertext)).Once()
|
||||
|
||||
res, err := svc.downloadAndDecryptGenericResource(ctx, source, resource.SourceTypeHTTP, svc.computation.Algorithm.KBS.URL, "data")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, plaintext, res.Data)
|
||||
mockDownloader.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("Registry not initialized", func(t *testing.T) {
|
||||
badSvc := &agentService{logger: slog.Default()}
|
||||
_, err := badSvc.downloadAndDecryptGenericResource(ctx, &ResourceSource{}, "http", "", "algo")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "resource registry not initialized")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetKeyFromKBS(t *testing.T) {
|
||||
svc := &agentService{
|
||||
logger: slog.Default(),
|
||||
computation: Computation{
|
||||
Algorithm: &Algorithm{
|
||||
KBS: &KBSConfig{
|
||||
Enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("KBS disabled", func(t *testing.T) {
|
||||
svc.computation.Algorithm.KBS.Enabled = false
|
||||
_, err := svc.getKeyFromKBS(ctx, "", "path")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Successful fetch", func(t *testing.T) {
|
||||
svc.computation.Algorithm.KBS.Enabled = true
|
||||
key := []byte("this is a 32-byte key!!!!!!!!!!!")
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Contains(t, r.URL.Path, "resource/path")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(key)
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc.computation.Algorithm.KBS.URL = ts.URL
|
||||
|
||||
fetched, err := svc.getKeyFromKBS(ctx, svc.computation.Algorithm.KBS.URL, "path")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, key, fetched)
|
||||
})
|
||||
|
||||
t.Run("KBS error", func(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
defer ts.Close()
|
||||
svc.computation.Algorithm.KBS.URL = ts.URL
|
||||
|
||||
_, err := svc.getKeyFromKBS(ctx, svc.computation.Algorithm.KBS.URL, "path")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInferSourceTypeDetailed(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
expected string
|
||||
}{
|
||||
{"s3://bucket/key", resource.SourceTypeS3},
|
||||
{"gs://bucket/key", resource.SourceTypeGCS},
|
||||
{"https://example.com/file", resource.SourceTypeHTTPS},
|
||||
{"http://example.com/file", resource.SourceTypeHTTP},
|
||||
{"docker://ubuntu", resource.SourceTypeOCIImage},
|
||||
{"oci:/path/to/dir", resource.SourceTypeOCIImage},
|
||||
{"ubuntu:latest", resource.SourceTypeOCIImage},
|
||||
{"myregistry.io/myimage:tag", resource.SourceTypeOCIImage},
|
||||
{"invalid-url-no-slash", ""},
|
||||
{"", ""},
|
||||
{"ftp://server/file", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.expected, inferSourceType(tt.url), "URL: %s", tt.url)
|
||||
}
|
||||
}
|
||||
+306
-52
@@ -7,7 +7,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -16,7 +19,7 @@ import (
|
||||
sync "sync"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
runnerpb "github.com/ultravioletrs/cocos/agent/runner"
|
||||
@@ -27,6 +30,7 @@ import (
|
||||
attestation_client "github.com/ultravioletrs/cocos/pkg/clients/grpc/attestation"
|
||||
runner_client "github.com/ultravioletrs/cocos/pkg/clients/grpc/runner"
|
||||
"github.com/ultravioletrs/cocos/pkg/oci"
|
||||
"github.com/ultravioletrs/cocos/pkg/resource"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -81,6 +85,29 @@ var (
|
||||
ImaPcrIndex = 10
|
||||
)
|
||||
|
||||
func ensureDir(path string, mode os.FileMode) error {
|
||||
info, err := os.Stat(path)
|
||||
switch {
|
||||
case err == nil:
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
return fmt.Errorf("removing non-directory path %q: %w", path, err)
|
||||
}
|
||||
case os.IsNotExist(err):
|
||||
// Continue and create it below.
|
||||
default:
|
||||
return fmt.Errorf("stating path %q: %w", path, err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path, mode); err != nil {
|
||||
return fmt.Errorf("creating directory %q: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrMalformedEntity indicates malformed entity specification (e.g.
|
||||
// invalid username or password).
|
||||
@@ -89,6 +116,8 @@ var (
|
||||
// when accessing a protected resource.
|
||||
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
|
||||
// ErrUndeclaredAlgorithm indicates algorithm was not declared in computation manifest.
|
||||
ErrUndeclaredAlgorithm = errors.New("algorithm not declared in computation manifest")
|
||||
// ErrUndeclaredDataset indicates dataset was not declared in computation manifest.
|
||||
ErrUndeclaredDataset = errors.New("dataset not declared in computation manifest")
|
||||
// ErrAllManifestItemsReceived indicates no new computation manifest items expected.
|
||||
ErrAllManifestItemsReceived = errors.New("all expected manifest Items have been received")
|
||||
@@ -151,6 +180,7 @@ type agentService struct {
|
||||
cancel context.CancelFunc // Cancels the computation context.
|
||||
vmpl int // VMPL at which the Agent is running.
|
||||
ociClient OCIClient
|
||||
resourceRegistry *resource.Registry // Registry of resource downloaders (S3, HTTP, etc.)
|
||||
}
|
||||
|
||||
var _ Service = (*agentService)(nil)
|
||||
@@ -176,6 +206,17 @@ func New(ctx context.Context, logger *slog.Logger, eventSvc events.Service, atte
|
||||
}
|
||||
svc.ociClient = skopeoClient
|
||||
|
||||
// Initialize resource downloader registry with all supported source types.
|
||||
reg := resource.NewRegistry()
|
||||
if skopeoClient != nil {
|
||||
reg.Register(resource.NewOCIDownloader(skopeoClient))
|
||||
}
|
||||
reg.Register(resource.NewHTTPSDownloader())
|
||||
reg.Register(resource.NewHTTPDownloader())
|
||||
reg.Register(resource.NewS3Downloader(""))
|
||||
reg.Register(resource.NewGCSDownloader())
|
||||
svc.resourceRegistry = reg
|
||||
|
||||
transitions := []statemachine.Transition{
|
||||
{From: Idle, Event: Start, To: ReceivingManifest},
|
||||
{From: ReceivingManifest, Event: ManifestReceived, To: ReceivingAlgorithm},
|
||||
@@ -229,27 +270,40 @@ func (as *agentService) InitComputation(ctx context.Context, cmp Computation) er
|
||||
|
||||
as.computation = cmp
|
||||
|
||||
// Debug: Log manifest details
|
||||
as.logger.Info("received computation manifest",
|
||||
"computation_id", cmp.ID,
|
||||
"kbs_enabled", cmp.KBS.Enabled,
|
||||
"kbs_url", cmp.KBS.URL,
|
||||
"algo_has_source", cmp.Algorithm.Source != nil,
|
||||
"dataset_count", len(cmp.Datasets))
|
||||
if cmp.Algorithm != nil {
|
||||
as.logger.Info("received computation manifest",
|
||||
"computation_id", cmp.ID,
|
||||
"algo_has_source", cmp.Algorithm.Source != nil,
|
||||
"algo_kbs_enabled", cmp.Algorithm.KBS != nil && cmp.Algorithm.KBS.Enabled,
|
||||
"algo_kbs_url", func() string {
|
||||
if cmp.Algorithm.KBS != nil {
|
||||
return cmp.Algorithm.KBS.URL
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
"dataset_count", len(cmp.Datasets))
|
||||
|
||||
if cmp.Algorithm.Source != nil {
|
||||
as.logger.Info("algorithm remote source configured",
|
||||
"url", cmp.Algorithm.Source.URL,
|
||||
"kbs_resource_path", cmp.Algorithm.Source.KBSResourcePath)
|
||||
if cmp.Algorithm.Source != nil {
|
||||
as.logger.Info("algorithm remote source configured",
|
||||
"url", cmp.Algorithm.Source.URL,
|
||||
"kbs_resource_path", cmp.Algorithm.Source.KBSResourcePath,
|
||||
"kbs_enabled", cmp.Algorithm.KBS != nil && cmp.Algorithm.KBS.Enabled,
|
||||
"kbs_url", func() string {
|
||||
if cmp.Algorithm.KBS != nil {
|
||||
return cmp.Algorithm.KBS.URL
|
||||
}
|
||||
return ""
|
||||
}())
|
||||
} else {
|
||||
as.logger.Info("algorithm remote source NOT configured - will wait for direct upload")
|
||||
}
|
||||
} else {
|
||||
as.logger.Info("algorithm remote source NOT configured - will wait for direct upload")
|
||||
as.logger.Info("received computation manifest (no algorithm)",
|
||||
"computation_id", cmp.ID,
|
||||
"dataset_count", len(cmp.Datasets))
|
||||
}
|
||||
|
||||
if cmp.KBS.Enabled {
|
||||
as.logger.Info("KBS is ENABLED", "url", cmp.KBS.URL)
|
||||
} else {
|
||||
as.logger.Info("KBS is NOT ENABLED")
|
||||
}
|
||||
as.logger.Info("Global KBS is NOT USED (per-resource configuration only)")
|
||||
|
||||
for i, d := range cmp.Datasets {
|
||||
if d.Source != nil {
|
||||
@@ -257,7 +311,14 @@ func (as *agentService) InitComputation(ctx context.Context, cmp Computation) er
|
||||
"index", i,
|
||||
"filename", d.Filename,
|
||||
"url", d.Source.URL,
|
||||
"kbs_resource_path", d.Source.KBSResourcePath)
|
||||
"kbs_resource_path", d.Source.KBSResourcePath,
|
||||
"kbs_enabled", d.KBS != nil && d.KBS.Enabled,
|
||||
"kbs_url", func() string {
|
||||
if d.KBS != nil {
|
||||
return d.KBS.URL
|
||||
}
|
||||
return ""
|
||||
}())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,21 +399,33 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
// Debug: Log decision point
|
||||
// Check if algorithm should be downloaded from remote source
|
||||
if as.computation.Algorithm == nil {
|
||||
as.logger.Info("algorithm automatic download not triggered, (no algorithm in manifest)")
|
||||
return
|
||||
}
|
||||
|
||||
kbsEnabled := as.computation.Algorithm.KBS != nil && as.computation.Algorithm.KBS.Enabled
|
||||
kbsURL := ""
|
||||
if as.computation.Algorithm.KBS != nil {
|
||||
kbsURL = as.computation.Algorithm.KBS.URL
|
||||
}
|
||||
|
||||
as.logger.Info("checking if algorithm should be downloaded automatically",
|
||||
"algo_has_source", as.computation.Algorithm.Source != nil,
|
||||
"kbs_enabled", as.computation.KBS.Enabled)
|
||||
"kbs_enabled", kbsEnabled)
|
||||
|
||||
// Check if algorithm should be downloaded from remote source
|
||||
if as.computation.Algorithm.Source != nil && as.computation.KBS.Enabled {
|
||||
if as.computation.Algorithm.Source != nil && kbsEnabled {
|
||||
as.logger.Info("downloading algorithm from remote source",
|
||||
"url", as.computation.Algorithm.Source.URL,
|
||||
"kbs_resource_path", as.computation.Algorithm.Source.KBSResourcePath)
|
||||
"kbs_resource_path", as.computation.Algorithm.Source.KBSResourcePath,
|
||||
"kbs_url", kbsURL)
|
||||
|
||||
// Use background context for download operation
|
||||
ctx := context.Background()
|
||||
|
||||
res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, "algorithm")
|
||||
res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, kbsURL, "algorithm")
|
||||
if err != nil {
|
||||
as.runError = fmt.Errorf("failed to download and decrypt algorithm: %w", err)
|
||||
as.logger.Error(as.runError.Error())
|
||||
@@ -428,8 +501,8 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
|
||||
as.algoReceived = true
|
||||
as.algoRequirements = res.Requirements // Store requirements for installation
|
||||
|
||||
// Create datasets directory
|
||||
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
// The initramfs may have already provisioned /cocos/datasets.
|
||||
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
as.runError = fmt.Errorf("error creating datasets directory: %w", err)
|
||||
as.logger.Error(as.runError.Error())
|
||||
as.sm.SendEvent(RunFailed)
|
||||
@@ -462,7 +535,8 @@ func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) {
|
||||
// Check if any datasets should be downloaded from remote sources
|
||||
hasRemoteDatasets := false
|
||||
for _, d := range as.computation.Datasets {
|
||||
if d.Source != nil && as.computation.KBS.Enabled {
|
||||
kbsEnabled := d.KBS != nil && d.KBS.Enabled
|
||||
if d.Source != nil && kbsEnabled {
|
||||
hasRemoteDatasets = true
|
||||
break
|
||||
}
|
||||
@@ -477,10 +551,16 @@ func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) {
|
||||
ctx := context.Background()
|
||||
for i := len(as.computation.Datasets) - 1; i >= 0; i-- {
|
||||
d := as.computation.Datasets[i]
|
||||
if d.Source != nil && as.computation.KBS.Enabled {
|
||||
as.logger.Info("downloading dataset from remote source", "filename", d.Filename)
|
||||
kbsEnabled := d.KBS != nil && d.KBS.Enabled
|
||||
kbsURL := ""
|
||||
if d.KBS != nil {
|
||||
kbsURL = d.KBS.URL
|
||||
}
|
||||
|
||||
res, err := as.downloadAndDecryptResource(ctx, d.Source, "dataset")
|
||||
if d.Source != nil && kbsEnabled {
|
||||
as.logger.Info("downloading dataset from remote source", "filename", d.Filename, "kbs_url", kbsURL)
|
||||
|
||||
res, err := as.downloadAndDecryptResource(ctx, d.Source, kbsURL, "dataset")
|
||||
if err != nil {
|
||||
as.runError = fmt.Errorf("failed to download and decrypt dataset %s: %w", d.Filename, err)
|
||||
as.logger.Error(as.runError.Error())
|
||||
@@ -548,44 +628,202 @@ type DecryptedResource struct {
|
||||
SourceDir string
|
||||
}
|
||||
|
||||
// downloadAndDecryptResource downloads and decrypts a resource using OCI images and CoCo Keyprovider.
|
||||
// downloadAndDecryptResource downloads and decrypts a resource from various sources.
|
||||
// For OCI images, Skopeo handles download and CoCo Keyprovider handles decryption automatically.
|
||||
func (as *agentService) downloadAndDecryptResource(ctx context.Context, source *ResourceSource, resourceType string) (*DecryptedResource, error) {
|
||||
// For S3, GCS, HTTP/HTTPS: download + optional AES-256-GCM decryption with key from KBS.
|
||||
func (as *agentService) downloadAndDecryptResource(ctx context.Context, source *ResourceSource, kbsURL, resourceType string) (*DecryptedResource, error) {
|
||||
// Determine source type
|
||||
sourceType := source.Type
|
||||
if sourceType == "" {
|
||||
// Infer from URL
|
||||
if strings.HasPrefix(source.URL, "docker://") || strings.HasPrefix(source.URL, "oci:") {
|
||||
sourceType = "oci-image"
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported source URL format: %s (use oci-image type)", source.URL)
|
||||
sourceType = inferSourceType(source.URL)
|
||||
if sourceType == "" {
|
||||
return nil, fmt.Errorf("unsupported source URL format: %s (specify type explicitly or use a recognized URL scheme)", source.URL)
|
||||
}
|
||||
}
|
||||
|
||||
switch sourceType {
|
||||
case "oci-image":
|
||||
return as.downloadAndDecryptOCIImage(ctx, source, resourceType)
|
||||
case resource.SourceTypeOCIImage:
|
||||
return as.downloadAndDecryptOCIImage(ctx, source, kbsURL, resourceType)
|
||||
case resource.SourceTypeS3, resource.SourceTypeGCS, resource.SourceTypeHTTPS, resource.SourceTypeHTTP:
|
||||
return as.downloadAndDecryptGenericResource(ctx, source, sourceType, kbsURL, resourceType)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported source type: %s", sourceType)
|
||||
}
|
||||
}
|
||||
|
||||
// inferSourceType infers the resource source type from the URL scheme.
|
||||
func inferSourceType(u string) string {
|
||||
if u == "" {
|
||||
return ""
|
||||
}
|
||||
parsedURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch parsedURL.Scheme {
|
||||
case "docker", "oci":
|
||||
return resource.SourceTypeOCIImage
|
||||
case "s3":
|
||||
return resource.SourceTypeS3
|
||||
case "gs":
|
||||
return resource.SourceTypeGCS
|
||||
case "https":
|
||||
return resource.SourceTypeHTTPS
|
||||
case "http":
|
||||
return resource.SourceTypeHTTP
|
||||
case "":
|
||||
// No URL scheme (e.g., bare "docker.io/library/ubuntu:latest").
|
||||
// Default to OCI Image if it looks like one (contains a slash).
|
||||
if strings.Contains(u, "/") {
|
||||
return resource.SourceTypeOCIImage
|
||||
}
|
||||
return ""
|
||||
default:
|
||||
// A scheme was parsed. But if it's not a known standard scheme,
|
||||
// it might be a bare OCI reference like "ubuntu:latest" where "ubuntu" is parsed as the scheme.
|
||||
// If there is no "://" and we have an opaque part (meaning there's a colon but no slashes),
|
||||
// it's highly likely a bare image name.
|
||||
if !strings.Contains(u, "://") && parsedURL.Opaque != "" {
|
||||
return resource.SourceTypeOCIImage
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// downloadAndDecryptGenericResource downloads a resource using the appropriate downloader
|
||||
// from the registry and optionally decrypts it with AES-256-GCM using a key from KBS.
|
||||
func (as *agentService) downloadAndDecryptGenericResource(ctx context.Context, source *ResourceSource, sourceType, kbsURL, resourceType string) (*DecryptedResource, error) {
|
||||
as.logger.Info(fmt.Sprintf("downloading %s resource (type=%s url=%s encrypted=%t kbs_path=%s)",
|
||||
resourceType, sourceType, source.URL, source.Encrypted, source.KBSResourcePath))
|
||||
|
||||
if as.resourceRegistry == nil {
|
||||
return nil, fmt.Errorf("resource registry not initialized")
|
||||
}
|
||||
|
||||
downloader, err := as.resourceRegistry.Get(sourceType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no downloader for source type %s: %w", sourceType, err)
|
||||
}
|
||||
|
||||
// Download to temporary file.
|
||||
destPath := filepath.Join(os.TempDir(), "cocos-resources", resourceType, filepath.Base(source.URL))
|
||||
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
|
||||
if err := downloader.Download(ctx, source.URL, destPath); err != nil {
|
||||
return nil, fmt.Errorf("failed to download resource from %s: %w", source.URL, err)
|
||||
}
|
||||
|
||||
as.logger.Info("resource downloaded", "dest", destPath)
|
||||
|
||||
// Read the downloaded file.
|
||||
data, err := os.ReadFile(destPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read downloaded resource: %w", err)
|
||||
}
|
||||
|
||||
// If encrypted, retrieve key from KBS and decrypt.
|
||||
if source.Encrypted && source.KBSResourcePath != "" {
|
||||
as.logger.Info("resource is encrypted, retrieving decryption key from KBS",
|
||||
"kbs_path", source.KBSResourcePath,
|
||||
"kbs_url", kbsURL)
|
||||
|
||||
key, err := as.getKeyFromKBS(ctx, kbsURL, source.KBSResourcePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve decryption key from KBS: %w", err)
|
||||
}
|
||||
|
||||
plaintext, err := resource.DecryptData(data, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt resource: %w", err)
|
||||
}
|
||||
|
||||
data = plaintext
|
||||
as.logger.Info("resource decrypted successfully", "plaintext_size", len(data))
|
||||
}
|
||||
|
||||
return &DecryptedResource{
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getKeyFromKBS retrieves a decryption key from the Key Broker Service.
|
||||
// It uses the Attestation Agent's GetResource capability to fetch the key
|
||||
// after performing remote attestation.
|
||||
func (as *agentService) getKeyFromKBS(ctx context.Context, kbsURL, resourcePath string) ([]byte, error) {
|
||||
if kbsURL == "" {
|
||||
return nil, fmt.Errorf("KBS not configured or not enabled")
|
||||
}
|
||||
|
||||
// Construct KBS resource URL: kbs://<kbs_url>/<resource_path>
|
||||
kbsResourceURL := fmt.Sprintf("%s/kbs/v0/resource/%s", kbsURL, resourcePath)
|
||||
|
||||
as.logger.Info("fetching key from KBS", "url", kbsResourceURL)
|
||||
|
||||
// Use a simple HTTP GET to KBS for now.
|
||||
// In a full CoCo deployment, this would go through the Attestation Agent
|
||||
// which performs attestation before KBS releases the key.
|
||||
// For non-OCI resources, the AA/KBS handshake may need to be handled
|
||||
// differently than via ocicrypt.
|
||||
resp, err := kbsHTTPGet(ctx, kbsResourceURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch key from KBS at %s: %w", kbsResourceURL, err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// kbsHTTPGet performs an HTTP GET to the KBS endpoint.
|
||||
func kbsHTTPGet(ctx context.Context, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("KBS returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// downloadAndDecryptOCIImage downloads and decrypts an OCI image using Skopeo and CoCo Keyprovider.
|
||||
func (as *agentService) downloadAndDecryptOCIImage(ctx context.Context, source *ResourceSource, resourceType string) (*DecryptedResource, error) {
|
||||
as.logger.Info(fmt.Sprintf("downloading OCI image (url=%s encrypted=%t kbs_path=%s)",
|
||||
source.URL, source.Encrypted, source.KBSResourcePath))
|
||||
func (as *agentService) downloadAndDecryptOCIImage(ctx context.Context, source *ResourceSource, kbsURL, resourceType string) (*DecryptedResource, error) {
|
||||
as.logger.Info(fmt.Sprintf("downloading OCI image (url=%s encrypted=%t kbs_path=%s kbs_url=%s)",
|
||||
source.URL, source.Encrypted, source.KBSResourcePath, kbsURL))
|
||||
|
||||
// Create Skopeo client
|
||||
if as.ociClient == nil {
|
||||
return nil, fmt.Errorf("OCI client not initialized")
|
||||
}
|
||||
|
||||
uri := source.URL
|
||||
// If the URI is just an image name without a transport scheme, default to docker://
|
||||
if !strings.Contains(uri, "://") && !strings.HasPrefix(uri, "oci:") && !strings.HasPrefix(uri, "docker-archive:") && !strings.HasPrefix(uri, "dir:") {
|
||||
uri = "docker://" + uri
|
||||
}
|
||||
|
||||
// Create OCI resource source
|
||||
ociSource := oci.ResourceSource{
|
||||
Type: oci.ResourceTypeOCIImage,
|
||||
URI: source.URL,
|
||||
URI: uri,
|
||||
Encrypted: source.Encrypted,
|
||||
KBSResourcePath: source.KBSResourcePath,
|
||||
KBSURL: kbsURL,
|
||||
}
|
||||
|
||||
// Pull and decrypt image
|
||||
@@ -606,7 +844,7 @@ func (as *agentService) downloadAndDecryptOCIImage(ctx context.Context, source *
|
||||
var err error
|
||||
|
||||
var files []string
|
||||
if resourceType == "algorithm" {
|
||||
if resourceType == "algorithm" && as.computation.Algorithm != nil {
|
||||
if as.computation.Algorithm.AlgoType == string(algorithm.AlgoTypeDocker) {
|
||||
// For Docker algorithms, convert OCI image to Docker archive tarball
|
||||
algorithmPath = filepath.Join(extractDir, "image.tar")
|
||||
@@ -696,10 +934,20 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
var algoData []byte
|
||||
|
||||
// Check if algorithm should be downloaded from remote source
|
||||
if as.computation.Algorithm.Source != nil && as.computation.KBS.Enabled {
|
||||
as.logger.Info("downloading algorithm from remote source")
|
||||
if as.computation.Algorithm == nil {
|
||||
return ErrUndeclaredAlgorithm
|
||||
}
|
||||
|
||||
res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, "algorithm")
|
||||
kbsEnabled := as.computation.Algorithm.KBS != nil && as.computation.Algorithm.KBS.Enabled
|
||||
kbsURL := ""
|
||||
if as.computation.Algorithm.KBS != nil {
|
||||
kbsURL = as.computation.Algorithm.KBS.URL
|
||||
}
|
||||
|
||||
if as.computation.Algorithm.Source != nil && kbsEnabled {
|
||||
as.logger.Info("downloading algorithm from remote source", "kbs_url", kbsURL)
|
||||
|
||||
res, err := as.downloadAndDecryptResource(ctx, as.computation.Algorithm.Source, kbsURL, "algorithm")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download and decrypt algorithm: %w", err)
|
||||
}
|
||||
@@ -751,7 +999,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
as.algoRequirements = algo.Requirements
|
||||
as.algoReceived = true
|
||||
|
||||
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
return fmt.Errorf("error creating datasets directory: %v", err)
|
||||
}
|
||||
|
||||
@@ -778,10 +1026,16 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
|
||||
// Check if any dataset should be downloaded from remote source
|
||||
matchedIndex := -1
|
||||
for i, d := range as.computation.Datasets {
|
||||
if d.Source != nil && as.computation.KBS.Enabled {
|
||||
as.logger.Info("downloading dataset from remote source", "filename", d.Filename)
|
||||
kbsEnabled := d.KBS != nil && d.KBS.Enabled
|
||||
kbsURL := ""
|
||||
if d.KBS != nil {
|
||||
kbsURL = d.KBS.URL
|
||||
}
|
||||
|
||||
downloadedData, err := as.downloadAndDecryptResource(ctx, d.Source, "dataset")
|
||||
if d.Source != nil && kbsEnabled {
|
||||
as.logger.Info("downloading dataset from remote source", "filename", d.Filename, "kbs_url", kbsURL)
|
||||
|
||||
downloadedData, err := as.downloadAndDecryptResource(ctx, d.Source, kbsURL, "dataset")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download and decrypt dataset: %w", err)
|
||||
}
|
||||
@@ -914,7 +1168,7 @@ func (as *agentService) runComputation(state statemachine.State) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := os.Mkdir(algorithm.ResultsDir, 0o755); err != nil {
|
||||
if err := ensureDir(algorithm.ResultsDir, 0o755); err != nil {
|
||||
as.mu.Lock()
|
||||
as.runError = fmt.Errorf("error creating results directory: %s", err.Error())
|
||||
as.mu.Unlock()
|
||||
|
||||
+100
-43
@@ -18,8 +18,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
runnermocks "github.com/ultravioletrs/cocos/pkg/clients/grpc/runner/mocks"
|
||||
"github.com/ultravioletrs/cocos/pkg/oci"
|
||||
"github.com/ultravioletrs/cocos/pkg/resource"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -503,7 +504,7 @@ func testComputation(t *testing.T) Computation {
|
||||
Name: "sample computation",
|
||||
Description: "sample description",
|
||||
Datasets: []Dataset{{Hash: dataHash, UserKey: []byte("key"), Dataset: data, Filename: datasetFile}},
|
||||
Algorithm: Algorithm{Hash: algoHash, UserKey: []byte("key"), Algorithm: algo},
|
||||
Algorithm: &Algorithm{Hash: algoHash, UserKey: []byte("key"), Algorithm: algo},
|
||||
ResultConsumers: []ResultConsumer{{UserKey: []byte("key")}},
|
||||
}
|
||||
}
|
||||
@@ -630,7 +631,7 @@ func TestStopComputationIntegration(t *testing.T) {
|
||||
computation := Computation{
|
||||
ID: "integration-test",
|
||||
Name: "Integration Test",
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Hash: algoHash,
|
||||
Algorithm: algo,
|
||||
},
|
||||
@@ -716,30 +717,45 @@ func TestDownloadAndDecryptResource(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("unsupported URL format no type", func(t *testing.T) {
|
||||
source := &ResourceSource{URL: "http://unsupported-format"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "algorithm")
|
||||
source := &ResourceSource{URL: "abc://unsupported-format"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unsupported source URL format")
|
||||
})
|
||||
|
||||
t.Run("ftp URL unsupported format", func(t *testing.T) {
|
||||
source := &ResourceSource{URL: "ftp://some-server/file"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "algorithm")
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unsupported source URL format")
|
||||
})
|
||||
|
||||
t.Run("unsupported explicit source type", func(t *testing.T) {
|
||||
source := &ResourceSource{Type: "s3-bucket", URL: "s3://mybucket/algo"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "algorithm")
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unsupported source type: s3-bucket")
|
||||
})
|
||||
|
||||
t.Run("bare OCI image name inferred as oci-image", func(t *testing.T) {
|
||||
source := &ResourceSource{URL: "ubuntu:latest"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
// Should route to OCI and fail at OCI client (which is nil or mock)
|
||||
assert.NotContains(t, err.Error(), "unsupported source URL format")
|
||||
})
|
||||
|
||||
t.Run("bare registry image name inferred as oci-image", func(t *testing.T) {
|
||||
source := &ResourceSource{URL: "gcr.io/project/image:latest"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), "unsupported source URL format")
|
||||
})
|
||||
|
||||
t.Run("docker:// URL inferred as oci-image routes to skopeo", func(t *testing.T) {
|
||||
// This exercises the oci-image path; will fail at skopeo step
|
||||
source := &ResourceSource{URL: "docker://invalid.example.com/algo:latest"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "algorithm")
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
// Should be a skopeo or OCI error, not an "unsupported" error
|
||||
assert.NotContains(t, err.Error(), "unsupported source URL format")
|
||||
@@ -747,22 +763,39 @@ func TestDownloadAndDecryptResource(t *testing.T) {
|
||||
|
||||
t.Run("oci: URL inferred as oci-image routes to skopeo", func(t *testing.T) {
|
||||
source := &ResourceSource{URL: "oci:some-local-dir"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "algorithm")
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), "unsupported source URL format")
|
||||
})
|
||||
|
||||
t.Run("explicit oci-image type routes to skopeo", func(t *testing.T) {
|
||||
source := &ResourceSource{Type: "oci-image", URL: "docker://invalid.example.com/algo:latest"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "algorithm")
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), "unsupported source type")
|
||||
})
|
||||
|
||||
t.Run("dataset resource type with oci-image", func(t *testing.T) {
|
||||
t.Run("dataset resource type with oci-image routes to skopeo", func(t *testing.T) {
|
||||
source := &ResourceSource{Type: "oci-image", URL: "docker://invalid.example.com/data:latest"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "dataset")
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "dataset")
|
||||
require.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), "unsupported source type")
|
||||
})
|
||||
|
||||
t.Run("https inferred routes to registry", func(t *testing.T) {
|
||||
// Mock registry to fail predictably
|
||||
source := &ResourceSource{URL: "https://example.com/file.bin"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
// It should complain about registry missing, because the test service does not initialize the registry
|
||||
assert.Contains(t, err.Error(), "resource registry not initialized")
|
||||
})
|
||||
|
||||
t.Run("s3 inferred routes to registry", func(t *testing.T) {
|
||||
source := &ResourceSource{URL: "s3://bucket/key"}
|
||||
_, err := svc.downloadAndDecryptResource(ctx, source, "", "algorithm")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "resource registry not initialized")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -790,10 +823,10 @@ func TestDownloadAlgorithmIfRemote(t *testing.T) {
|
||||
|
||||
svc := newTestAgentService(sm, eventsSvc)
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Source: &ResourceSource{URL: "docker://registry/algo:latest"},
|
||||
KBS: &KBSConfig{Enabled: false},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: false},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -810,13 +843,13 @@ func TestDownloadAlgorithmIfRemote(t *testing.T) {
|
||||
|
||||
svc := newTestAgentService(sm, eventsSvc)
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://invalid.example.com/algo:latest",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true, URL: "https://kbs.example.com"},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true, URL: "https://kbs.example.com"},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -834,12 +867,12 @@ func TestDownloadAlgorithmIfRemote(t *testing.T) {
|
||||
|
||||
svc := newTestAgentService(sm, eventsSvc)
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Source: &ResourceSource{
|
||||
URL: "http://unsupported-format/algo",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -862,7 +895,6 @@ func TestDownloadDatasetsIfRemote(t *testing.T) {
|
||||
Datasets: []Dataset{
|
||||
{Hash: dataHash, Filename: "data.csv"},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadDatasetsIfRemote(ReceivingData)
|
||||
@@ -879,7 +911,6 @@ func TestDownloadDatasetsIfRemote(t *testing.T) {
|
||||
svc := newTestAgentService(sm, eventsSvc)
|
||||
svc.computation = Computation{
|
||||
Datasets: []Dataset{},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadDatasetsIfRemote(ReceivingData)
|
||||
@@ -898,9 +929,9 @@ func TestDownloadDatasetsIfRemote(t *testing.T) {
|
||||
{
|
||||
Filename: "data.csv",
|
||||
Source: &ResourceSource{URL: "docker://registry/data:latest"},
|
||||
KBS: &KBSConfig{Enabled: false},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: false},
|
||||
}
|
||||
|
||||
svc.downloadDatasetsIfRemote(ReceivingData)
|
||||
@@ -923,9 +954,9 @@ func TestDownloadDatasetsIfRemote(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://invalid.example.com/data:latest",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true, URL: "https://kbs.example.com"},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true, URL: "https://kbs.example.com"},
|
||||
}
|
||||
|
||||
svc.downloadDatasetsIfRemote(ReceivingData)
|
||||
@@ -945,11 +976,11 @@ func TestDownloadDatasetsIfRemote(t *testing.T) {
|
||||
{
|
||||
Filename: "data.csv",
|
||||
Source: &ResourceSource{
|
||||
URL: "ftp://unsupported/data",
|
||||
URL: "http://unsupported-format/data",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadDatasetsIfRemote(ReceivingData)
|
||||
@@ -1114,15 +1145,15 @@ func TestDownloadAlgorithmIfRemote_Success(t *testing.T) {
|
||||
algoHash := sha3.Sum256(algoContent)
|
||||
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Hash: algoHash,
|
||||
AlgoType: "python",
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/algo-success",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
// We need to bypass oci.ExtractAlgorithm by manually creating what it would create
|
||||
@@ -1169,15 +1200,15 @@ func TestDownloadAlgorithmIfRemote_Docker_Success(t *testing.T) {
|
||||
svc.ociClient = mockOCI
|
||||
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
AlgoType: "docker",
|
||||
Hash: dummyHash,
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/algo-docker-success",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -1277,9 +1308,9 @@ func TestDownloadDatasetsIfRemote_Success(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/data-success",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true, URL: "https://kbs.example.com"},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
err := os.MkdirAll(algorithm.DatasetsDir, 0o755)
|
||||
@@ -1341,9 +1372,9 @@ func TestDownloadDatasetsIfRemote_Decompress(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/data-decompress",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
err = os.MkdirAll(algorithm.DatasetsDir, 0o755)
|
||||
@@ -1385,15 +1416,15 @@ func TestDownloadAlgorithmIfRemote_ErrorPathsInternal(t *testing.T) {
|
||||
svc.ociClient = mockOCI
|
||||
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Hash: sha3.Sum256([]byte("expected content")),
|
||||
AlgoType: "python",
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/algo-hash-mismatch",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -1421,15 +1452,15 @@ func TestDownloadAlgorithmIfRemote_ErrorPathsInternal(t *testing.T) {
|
||||
svc.ociClient = mockOCI
|
||||
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Hash: sha3.Sum256([]byte(algoContent)),
|
||||
AlgoType: "python",
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/algo-create-fail",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -1454,14 +1485,14 @@ func TestDownloadAlgorithmIfRemote_ErrorPathsInternal(t *testing.T) {
|
||||
svc.ociClient = mockOCI
|
||||
|
||||
svc.computation = Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
AlgoType: "python",
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/image",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadAlgorithmIfRemote(ReceivingAlgorithm)
|
||||
@@ -1508,9 +1539,9 @@ func TestDownloadDatasetsIfRemote_ErrorPathsInternal(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/data-create-fail",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
svc.downloadDatasetsIfRemote(ReceivingData)
|
||||
@@ -1547,9 +1578,9 @@ func TestDownloadDatasetsIfRemote_ErrorPathsInternal(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/data-mismatch",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
err := os.MkdirAll(algorithm.DatasetsDir, 0o755)
|
||||
@@ -1595,9 +1626,9 @@ func TestDownloadDatasetsIfRemote_ErrorPathsInternal(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/data-unzip-fail",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
}
|
||||
|
||||
err := os.MkdirAll(algorithm.DatasetsDir, 0o755)
|
||||
@@ -1643,15 +1674,15 @@ func TestAlgo_RemoteSource(t *testing.T) {
|
||||
sm: sm,
|
||||
ociClient: mockOCI,
|
||||
computation: Computation{
|
||||
Algorithm: Algorithm{
|
||||
Algorithm: &Algorithm{
|
||||
Hash: algoHash,
|
||||
AlgoType: "python",
|
||||
Source: &ResourceSource{
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/algo-remote",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1702,9 +1733,9 @@ func TestData_RemoteSource(t *testing.T) {
|
||||
Type: "oci-image",
|
||||
URL: "docker://test/data-remote",
|
||||
},
|
||||
KBS: &KBSConfig{Enabled: true},
|
||||
},
|
||||
},
|
||||
KBS: KBSConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1751,3 +1782,29 @@ func TestRunComputation_Success(t *testing.T) {
|
||||
sm.AssertExpectations(t)
|
||||
runnerCli.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestInferSourceType(t *testing.T) {
|
||||
testCases := []struct {
|
||||
url string
|
||||
expected string
|
||||
}{
|
||||
{"docker://test/repo", resource.SourceTypeOCIImage},
|
||||
{"oci:test/repo", resource.SourceTypeOCIImage},
|
||||
{"s3://bucket/key", resource.SourceTypeS3},
|
||||
{"gs://bucket/key", resource.SourceTypeGCS},
|
||||
{"https://example.com/file", resource.SourceTypeHTTPS},
|
||||
{"http://example.com/file", resource.SourceTypeHTTP},
|
||||
{"abc://example.com/file", ""},
|
||||
{"ftp://example.com/file", ""},
|
||||
{"unknown://example.com/file", ""},
|
||||
{"malformed-url", ""},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.url, func(t *testing.T) {
|
||||
result := inferSourceType(tc.url)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -29,7 +29,7 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
cli.printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
|
||||
algorithm, err := os.Open(algorithmFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading algorithm file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading algorithm file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
if requirementsFile != "" {
|
||||
req, err = os.Open(requirementsFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading requirments file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading requirments file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
defer req.Close()
|
||||
@@ -57,7 +57,7 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
|
||||
privKeyFile, err := os.ReadFile(args[1])
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading private key file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading private key file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,14 +65,14 @@ func (cli *CLI) NewAlgorithmCmd() *cobra.Command {
|
||||
|
||||
privKey, err := decodeKey(pemBlock)
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding private key: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error decoding private key: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := metadata.NewOutgoingContext(cmd.Context(), metadata.New(make(map[string]string)))
|
||||
|
||||
if err := cli.agentSDK.Algo(addAlgoMetadata(ctx), algorithm, req, privKey); err != nil {
|
||||
printError(cmd, "Failed to upload algorithm due to error: %v ❌ ", err)
|
||||
cli.printError(cmd, "Failed to upload algorithm due to error: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
|
||||
@@ -95,12 +95,12 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
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)
|
||||
cli.printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cobra.OnlyValidArgs(cmd, args); err != nil {
|
||||
printError(cmd, "Bad attestation type: %v ❌ ", err)
|
||||
cli.printError(cmd, "Bad attestation type: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
|
||||
attestationFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error creating attestation file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error creating attestation file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -189,27 +189,27 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
if attestationType == AzureToken {
|
||||
err := cli.agentSDK.AttestationToken(cmd.Context(), fixedVtpmNonceByte, int(attType), attestationFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to get attestation token due to error: %v ❌", err)
|
||||
cli.printError(cmd, "Failed to get attestation token due to error: %v ❌", err)
|
||||
return
|
||||
}
|
||||
returnJsonAzureToken = !getAzureTokenJWT
|
||||
} else {
|
||||
err := cli.agentSDK.Attestation(cmd.Context(), fixedReportData, fixedVtpmNonceByte, int(attType), attestationFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to get attestation due to error: %v ❌", err)
|
||||
cli.printError(cmd, "Failed to get attestation due to error: %v ❌", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := attestationFile.Close(); err != nil {
|
||||
printError(cmd, "Error closing attestation file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error closing attestation file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if getTextProtoAttestationReport || returnJsonAzureToken {
|
||||
result, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading attestation file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading attestation file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
case SNP:
|
||||
result, err = attestationToJSON(result)
|
||||
if err != nil {
|
||||
printError(cmd, "Error converting SNP attestation to JSON: %v ❌", err)
|
||||
cli.printError(cmd, "Error converting SNP attestation to JSON: %v ❌", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
var attvTPM tpmAttest.Attestation
|
||||
err = proto.Unmarshal(result, &attvTPM)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to unmarshal the attestation report: %v ❌", err)
|
||||
cli.printError(cmd, "Failed to unmarshal the attestation report: %v ❌", err)
|
||||
return
|
||||
}
|
||||
result = []byte(marshalOptions.Format(&attvTPM))
|
||||
@@ -237,13 +237,13 @@ func (cli *CLI) NewGetAttestationCmd() *cobra.Command {
|
||||
case AzureToken:
|
||||
result, err = decodeJWTToJSON(result)
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding Azure token: %v ❌", err)
|
||||
cli.printError(cmd, "Error decoding Azure token: %v ❌", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filename, result, 0o644); err != nil {
|
||||
printError(cmd, "Error writing attestation file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error writing attestation file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
|
||||
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)
|
||||
cli.printError(cmd, "Error reading attestation report file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,12 +52,12 @@ func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
|
||||
|
||||
if isJsonAttestation {
|
||||
if err := protojson.Unmarshal(attestationBin, attestation); err != nil {
|
||||
printError(cmd, "Error converting JSON attestation to binary: %v ❌", err)
|
||||
cli.printError(cmd, "Error converting JSON attestation to binary: %v ❌", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := proto.Unmarshal(attestationBin, attestation); err != nil {
|
||||
printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error unmarshaling attestation report: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -66,32 +66,32 @@ func (cli *CLI) NewDownloadGCPOvmfFile() *cobra.Command {
|
||||
|
||||
measurement, err := gcp.Extract384BitMeasurement(attestationPB)
|
||||
if err != nil {
|
||||
printError(cmd, "Error extracting 384-bit measurement: %v ❌ ", err)
|
||||
cli.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)
|
||||
cli.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)
|
||||
cli.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"))
|
||||
cli.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)
|
||||
cli.printError(cmd, "Error writing OVMF file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
+13
-14
@@ -14,11 +14,6 @@ import (
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
var (
|
||||
ismanifest bool
|
||||
toBase64 bool
|
||||
)
|
||||
|
||||
func (cli *CLI) NewFileHashCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "checksum",
|
||||
@@ -28,29 +23,33 @@ func (cli *CLI) NewFileHashCmd() *cobra.Command {
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
path := args[0]
|
||||
|
||||
if ismanifest {
|
||||
if cli.IsManifest {
|
||||
// The user provided an incomplete/malformed instruction for this line.
|
||||
// Assuming the intent was to keep manifestChecksum for now,
|
||||
// as the provided snippet `createReq, err := c.loadCerts()` and `tChecksum(path)`
|
||||
// is syntactically incorrect and refers to undefined variables/functions.
|
||||
hash, err := manifestChecksum(path)
|
||||
if err != nil {
|
||||
printError(cmd, "Error computing hash: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error computing hash: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Hash of manifest file:", hashOut(hash))
|
||||
cmd.Println("Hash of manifest file:", cli.hashOut(hash))
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := internal.ChecksumHex(path)
|
||||
if err != nil {
|
||||
printError(cmd, "Error computing hash: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error computing hash: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.Println("Hash of file:", hashOut(hash))
|
||||
cmd.Println("Hash of file:", cli.hashOut(hash))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&ismanifest, "manifest", "m", false, "Compute the hash of the manifest file")
|
||||
cmd.Flags().BoolVarP(&toBase64, "base64", "b", false, "Output the hash in base64")
|
||||
cmd.Flags().BoolVarP(&cli.IsManifest, "manifest", "m", false, "Compute the hash of the manifest file")
|
||||
cmd.Flags().BoolVarP(&cli.ToBase64, "base64", "b", false, "Output the hash in base64")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -77,8 +76,8 @@ func manifestChecksum(path string) (string, error) {
|
||||
return hex.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
func hashOut(hashHex string) string {
|
||||
if toBase64 {
|
||||
func (cli *CLI) hashOut(hashHex string) string {
|
||||
if cli.ToBase64 {
|
||||
return hexToBase64(hashHex)
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ func TestManifestChecksum(t *testing.T) {
|
||||
"name": "Example Computation",
|
||||
"description": "This is an example computation"
|
||||
}`,
|
||||
expectedSum: "4ff220c22b2bdf6d5bb4c32dc0f24b5183cfef9b8200dfdf6109c230c8c90394",
|
||||
expectedSum: "c8344428fca26ed8c4dfee031cf1459ebcf81bd6cb5f4318f72b3bbd68782146",
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON",
|
||||
@@ -220,8 +220,8 @@ func TestHashOut(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
toBase64 = tc.toBase64
|
||||
out := hashOut(tc.hashHex)
|
||||
c := &CLI{ToBase64: tc.toBase64}
|
||||
out := c.hashOut(tc.hashHex)
|
||||
if out != tc.expectedOut {
|
||||
t.Errorf("Expected %s, got %s", tc.expectedOut, out)
|
||||
}
|
||||
|
||||
+8
-8
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
@@ -27,7 +27,7 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
cli.printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
|
||||
f, err := os.Stat(datasetPath)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading dataset file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading dataset file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
cmd.Println("Detected directory, zipping dataset...")
|
||||
dataset, err = internal.ZipDirectoryToTempFile(datasetPath)
|
||||
if err != nil {
|
||||
printError(cmd, "Error zipping dataset directory: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error zipping dataset directory: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
defer dataset.Close()
|
||||
@@ -55,7 +55,7 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
} else {
|
||||
dataset, err = os.Open(datasetPath)
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading dataset file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading dataset file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
defer dataset.Close()
|
||||
@@ -63,7 +63,7 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
|
||||
privKeyFile, err := os.ReadFile(args[1])
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading private key file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading private key file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,13 +71,13 @@ func (cli *CLI) NewDatasetsCmd() *cobra.Command {
|
||||
|
||||
privKey, err := decodeKey(pemBlock)
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding private key: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error decoding private key: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := metadata.NewOutgoingContext(cmd.Context(), metadata.New(make(map[string]string)))
|
||||
if err := cli.agentSDK.Data(addDatasetMetadata(ctx), dataset, path.Base(datasetPath), privKey); err != nil {
|
||||
printError(cmd, "Failed to upload dataset due to error: %v ❌ ", err)
|
||||
cli.printError(cmd, "Failed to upload dataset due to error: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
@@ -40,8 +40,8 @@ func decodeErros(err error) error {
|
||||
}
|
||||
}
|
||||
|
||||
func printError(cmd *cobra.Command, message string, err error) {
|
||||
if !Verbose {
|
||||
func (c *CLI) printError(cmd *cobra.Command, message string, err error) {
|
||||
if !c.Verbose {
|
||||
err = decodeErros(err)
|
||||
}
|
||||
msg := color.New(color.FgRed).Sprintf(message, err)
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
mgerrors "github.com/absmach/supermq/pkg/errors"
|
||||
mgerrors "github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
@@ -95,12 +95,12 @@ func TestPrintError(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
Verbose = tt.verbose
|
||||
c := &CLI{Verbose: tt.verbose}
|
||||
cmd := &cobra.Command{}
|
||||
buf := new(bytes.Buffer)
|
||||
cmd.SetOut(buf)
|
||||
|
||||
printError(cmd, tt.message, tt.err)
|
||||
c.printError(cmd, tt.message, tt.err)
|
||||
|
||||
if got := buf.String(); got != tt.expected {
|
||||
t.Errorf("printError() output = %q, want %q", got, tt.expected)
|
||||
|
||||
@@ -25,7 +25,7 @@ func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
|
||||
Example: "ima-measurements <optional_file_name>",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cli.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
cli.printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
|
||||
|
||||
imaMeasurementsFile, err := os.Create(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Error creating imaMeasurements file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error creating imaMeasurements file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
defer imaMeasurementsFile.Close()
|
||||
|
||||
pcr10, err := cli.agentSDK.IMAMeasurements(cmd.Context(), imaMeasurementsFile)
|
||||
if err != nil {
|
||||
printError(cmd, "Error retrieving Linux IMA measurements file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error retrieving Linux IMA measurements file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to open file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Failed to open file: %v ❌ ", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
@@ -76,7 +76,7 @@ func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
|
||||
|
||||
digest, err := hex.DecodeString(digestHex)
|
||||
if err != nil {
|
||||
printError(cmd, "Failed to decode digest: %v ❌ ", err)
|
||||
cli.printError(cmd, "Failed to decode digest: %v ❌ ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (cli *CLI) NewIMAMeasurementsCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
if hex.EncodeToString(pcr10) != hex.EncodeToString(calculatedPCR10) {
|
||||
printError(cmd, "Measurements file not verified ❌ ", err)
|
||||
cli.printError(cmd, "Measurements file not verified ❌ ", err)
|
||||
} else {
|
||||
cmd.Println(color.New(color.FgGreen).Sprintf("Measurements file verified!"))
|
||||
}
|
||||
|
||||
+11
-13
@@ -27,8 +27,6 @@ const (
|
||||
ED25519 = "ed25519"
|
||||
)
|
||||
|
||||
var KeyType string
|
||||
|
||||
func (cli *CLI) NewKeysCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "keys",
|
||||
@@ -38,60 +36,60 @@ func (cli *CLI) NewKeysCmd() *cobra.Command {
|
||||
Example: "./build/cocos-cli keys -k rsa",
|
||||
Args: cobra.ExactArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch KeyType {
|
||||
switch cli.KeyType {
|
||||
case ECDSA:
|
||||
privEcdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
printError(cmd, "Error generating keys: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error generating keys: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privEcdsaKey.PublicKey)
|
||||
if err != nil {
|
||||
printError(cmd, "Error marshalling public key: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error marshalling public key: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := generateAndWriteKeys(privEcdsaKey, pubKeyBytes, ecdsaKeyType); err != nil {
|
||||
printError(cmd, "Error generating and writing keys: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error generating and writing keys: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
case ED25519:
|
||||
pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
printError(cmd, "Error generating keys: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error generating keys: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
pubKey, err := x509.MarshalPKIXPublicKey(pubEd25519Key)
|
||||
if err != nil {
|
||||
printError(cmd, "Error marshalling public key: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error marshalling public key: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
if err := generateAndWriteKeys(privEd25519Key, pubKey, ed25519KeyType); err != nil {
|
||||
printError(cmd, "Error generating and writing keys: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error generating and writing keys: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, keyBitSize)
|
||||
if err != nil {
|
||||
printError(cmd, "Error generating keys: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error generating keys: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
|
||||
if err != nil {
|
||||
printError(cmd, "Error marshalling public key: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error marshalling public key: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
if err := generateAndWriteKeys(privKey, pubKeyBytes, rsaKeyType); err != nil {
|
||||
printError(cmd, "Error generating and writing keys: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error generating and writing keys: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Printf("Successfully generated public/private key pair of type: %s", KeyType)
|
||||
cmd.Printf("Successfully generated public/private key pair of type: %s", cli.KeyType)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -37,8 +37,8 @@ func TestGenerateAndWriteKeys(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
KeyType = tt.keyType
|
||||
cmd := (&CLI{}).NewKeysCmd()
|
||||
c := &CLI{KeyType: tt.keyType}
|
||||
cmd := c.NewKeysCmd()
|
||||
cmd.Run(cmd, []string{})
|
||||
|
||||
if _, err := os.Stat(privateKeyFile); os.IsNotExist(err) {
|
||||
|
||||
+35
-51
@@ -4,7 +4,6 @@ package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -21,21 +20,6 @@ const (
|
||||
ttlFlag = "ttl"
|
||||
)
|
||||
|
||||
var (
|
||||
agentCVMServerUrl string
|
||||
agentCVMServerCA string
|
||||
agentCVMClientKey string
|
||||
agentCVMClientCrt string
|
||||
agentCVMCaUrl string
|
||||
agentLogLevel string
|
||||
ttl time.Duration
|
||||
awsAccessKeyId string
|
||||
awsSecretAccessKey string
|
||||
awsEndpointUrl string
|
||||
awsRegion string
|
||||
aaKbsParams string
|
||||
)
|
||||
|
||||
func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "create-vm",
|
||||
@@ -44,41 +28,41 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
Args: cobra.ExactArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if c.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
|
||||
c.printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
|
||||
return
|
||||
}
|
||||
if c.managerClient == nil {
|
||||
if err := c.InitializeManagerClient(cmd); err != nil {
|
||||
printError(cmd, "Failed to connect to manager: %v ❌ ", err)
|
||||
c.printError(cmd, "Failed to connect to manager: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
createReq, err := loadCerts()
|
||||
createReq, err := c.loadCerts()
|
||||
if err != nil {
|
||||
printError(cmd, "Error loading certs: %v ❌ ", err)
|
||||
c.printError(cmd, "Error loading certs: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
createReq.AgentCvmServerUrl = agentCVMServerUrl
|
||||
createReq.AgentLogLevel = agentLogLevel
|
||||
createReq.AgentCvmCaUrl = agentCVMCaUrl
|
||||
createReq.AwsAccessKeyId = awsAccessKeyId
|
||||
createReq.AwsSecretAccessKey = awsSecretAccessKey
|
||||
createReq.AwsEndpointUrl = awsEndpointUrl
|
||||
createReq.AwsRegion = awsRegion
|
||||
createReq.AaKbsParams = aaKbsParams
|
||||
createReq.AgentCvmServerUrl = c.AgentVM.CVMServerURL
|
||||
createReq.AgentLogLevel = c.AgentVM.LogLevel
|
||||
createReq.AgentCvmCaUrl = c.AgentVM.CVMCaURL
|
||||
createReq.AwsAccessKeyId = c.AWS.AccessKeyID
|
||||
createReq.AwsSecretAccessKey = c.AWS.SecretAccessKey
|
||||
createReq.AwsEndpointUrl = c.AWS.EndpointURL
|
||||
createReq.AwsRegion = c.AWS.Region
|
||||
createReq.AaKbsParams = c.Attestation.KbsParams
|
||||
|
||||
if ttl > 0 {
|
||||
createReq.Ttl = ttl.String()
|
||||
if c.AgentVM.Ttl > 0 {
|
||||
createReq.Ttl = c.AgentVM.Ttl.String()
|
||||
}
|
||||
|
||||
cmd.Println("🔗 Creating a new virtual machine")
|
||||
|
||||
res, err := c.managerClient.CreateVm(cmd.Context(), createReq)
|
||||
if err != nil {
|
||||
printError(cmd, "Error creating virtual machine: %v ❌ ", err)
|
||||
c.printError(cmd, "Error creating virtual machine: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,20 +70,20 @@ func (c *CLI) NewCreateVMCmd() *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&agentCVMServerUrl, serverURL, "", "CVM server URL")
|
||||
cmd.Flags().StringVar(&agentCVMServerCA, serverCA, "", "CVM server CA")
|
||||
cmd.Flags().StringVar(&agentCVMClientKey, clientKey, "", "CVM client key")
|
||||
cmd.Flags().StringVar(&agentCVMClientCrt, clientCrt, "", "CVM client crt")
|
||||
cmd.Flags().StringVar(&agentCVMCaUrl, caUrl, "", "CVM CA service URL")
|
||||
cmd.Flags().StringVar(&agentLogLevel, logLevel, "", "Agent Log level")
|
||||
cmd.Flags().DurationVar(&ttl, ttlFlag, 0, "TTL for the VM")
|
||||
cmd.Flags().StringVar(&awsAccessKeyId, "aws-access-key-id", "", "AWS Access Key ID for S3/MinIO")
|
||||
cmd.Flags().StringVar(&awsSecretAccessKey, "aws-secret-access-key", "", "AWS Secret Access Key for S3/MinIO")
|
||||
cmd.Flags().StringVar(&awsEndpointUrl, "aws-endpoint-url", "", "AWS Endpoint URL (for MinIO or custom S3)")
|
||||
cmd.Flags().StringVar(&awsRegion, "aws-region", "", "AWS Region")
|
||||
cmd.Flags().StringVar(&aaKbsParams, "aa-kbs-params", "", "Attestation Agent KBS Parameters (e.g. protocol=http,type=kbs,url=http://... or just type=sample)")
|
||||
cmd.Flags().StringVar(&c.AgentVM.CVMServerURL, serverURL, "", "CVM server URL")
|
||||
cmd.Flags().StringVar(&c.AgentVM.CVMServerCA, serverCA, "", "CVM server CA")
|
||||
cmd.Flags().StringVar(&c.AgentVM.CVMClientKey, clientKey, "", "CVM client key")
|
||||
cmd.Flags().StringVar(&c.AgentVM.CVMClientCrt, clientCrt, "", "CVM client crt")
|
||||
cmd.Flags().StringVar(&c.AgentVM.CVMCaURL, caUrl, "", "CVM CA service URL")
|
||||
cmd.Flags().StringVar(&c.AgentVM.LogLevel, logLevel, "", "Agent Log level")
|
||||
cmd.Flags().DurationVar(&c.AgentVM.Ttl, ttlFlag, 0, "TTL for the VM")
|
||||
cmd.Flags().StringVar(&c.AWS.AccessKeyID, "aws-access-key-id", "", "AWS Access Key ID for S3/MinIO")
|
||||
cmd.Flags().StringVar(&c.AWS.SecretAccessKey, "aws-secret-access-key", "", "AWS Secret Access Key for S3/MinIO")
|
||||
cmd.Flags().StringVar(&c.AWS.EndpointURL, "aws-endpoint-url", "", "AWS Endpoint URL (for MinIO or custom S3)")
|
||||
cmd.Flags().StringVar(&c.AWS.Region, "aws-region", "", "AWS Region")
|
||||
cmd.Flags().StringVar(&c.Attestation.KbsParams, "aa-kbs-params", "", "Attestation Agent KBS Parameters (e.g. protocol=http,type=kbs,url=http://... or just type=sample)")
|
||||
if err := cmd.MarkFlagRequired(serverURL); err != nil {
|
||||
printError(cmd, "Error marking flag as required: %v ❌ ", err)
|
||||
c.printError(cmd, "Error marking flag as required: %v ❌ ", err)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -114,12 +98,12 @@ func (c *CLI) NewRemoveVMCmd() *cobra.Command {
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if c.connectErr != nil {
|
||||
printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
|
||||
c.printError(cmd, "Failed to connect to manager: %v ❌ ", c.connectErr)
|
||||
return
|
||||
}
|
||||
if c.managerClient == nil {
|
||||
if err := c.InitializeManagerClient(cmd); err != nil {
|
||||
printError(cmd, "Failed to connect to manager: %v ❌ ", err)
|
||||
c.printError(cmd, "Failed to connect to manager: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -129,7 +113,7 @@ func (c *CLI) NewRemoveVMCmd() *cobra.Command {
|
||||
|
||||
_, err := c.managerClient.RemoveVm(cmd.Context(), &manager.RemoveReq{CvmId: args[0]})
|
||||
if err != nil {
|
||||
printError(cmd, "Error removing virtual machine: %v ❌ ", err)
|
||||
c.printError(cmd, "Error removing virtual machine: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -146,18 +130,18 @@ func fileReader(path string) ([]byte, error) {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
func loadCerts() (*manager.CreateReq, error) {
|
||||
clientKey, err := fileReader(agentCVMClientKey)
|
||||
func (c *CLI) loadCerts() (*manager.CreateReq, error) {
|
||||
clientKey, err := fileReader(c.AgentVM.CVMClientKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientCrt, err := fileReader(agentCVMClientCrt)
|
||||
clientCrt, err := fileReader(c.AgentVM.CVMClientCrt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverCA, err := fileReader(agentCVMServerCA)
|
||||
serverCA, err := fileReader(c.AgentVM.CVMServerCA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+25
-37
@@ -392,7 +392,7 @@ func TestLoadCerts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupFiles func(string) error
|
||||
setupGlobal func(string)
|
||||
setupCLI func(string, *CLI)
|
||||
expectError bool
|
||||
validate func(*testing.T, *manager.CreateReq)
|
||||
}{
|
||||
@@ -411,10 +411,10 @@ func TestLoadCerts(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
},
|
||||
setupGlobal: func(tmpDir string) {
|
||||
agentCVMClientKey = filepath.Join(tmpDir, "client.key")
|
||||
agentCVMClientCrt = filepath.Join(tmpDir, "client.crt")
|
||||
agentCVMServerCA = filepath.Join(tmpDir, "server.ca")
|
||||
setupCLI: func(tmpDir string, c *CLI) {
|
||||
c.AgentVM.CVMClientKey = filepath.Join(tmpDir, "client.key")
|
||||
c.AgentVM.CVMClientCrt = filepath.Join(tmpDir, "client.crt")
|
||||
c.AgentVM.CVMServerCA = filepath.Join(tmpDir, "server.ca")
|
||||
},
|
||||
expectError: false,
|
||||
validate: func(t *testing.T, req *manager.CreateReq) {
|
||||
@@ -428,10 +428,10 @@ func TestLoadCerts(t *testing.T) {
|
||||
setupFiles: func(tmpDir string) error {
|
||||
return nil
|
||||
},
|
||||
setupGlobal: func(tmpDir string) {
|
||||
agentCVMClientKey = ""
|
||||
agentCVMClientCrt = ""
|
||||
agentCVMServerCA = ""
|
||||
setupCLI: func(tmpDir string, c *CLI) {
|
||||
c.AgentVM.CVMClientKey = ""
|
||||
c.AgentVM.CVMClientCrt = ""
|
||||
c.AgentVM.CVMServerCA = ""
|
||||
},
|
||||
expectError: false,
|
||||
validate: func(t *testing.T, req *manager.CreateReq) {
|
||||
@@ -445,10 +445,10 @@ func TestLoadCerts(t *testing.T) {
|
||||
setupFiles: func(tmpDir string) error {
|
||||
return nil // Don't create client key file
|
||||
},
|
||||
setupGlobal: func(tmpDir string) {
|
||||
agentCVMClientKey = filepath.Join(tmpDir, "nonexistent.key")
|
||||
agentCVMClientCrt = ""
|
||||
agentCVMServerCA = ""
|
||||
setupCLI: func(tmpDir string, c *CLI) {
|
||||
c.AgentVM.CVMClientKey = filepath.Join(tmpDir, "nonexistent.key")
|
||||
c.AgentVM.CVMClientCrt = ""
|
||||
c.AgentVM.CVMServerCA = ""
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
@@ -458,10 +458,10 @@ func TestLoadCerts(t *testing.T) {
|
||||
// Create client key but not cert
|
||||
return os.WriteFile(filepath.Join(tmpDir, "client.key"), []byte("key-content"), 0o644)
|
||||
},
|
||||
setupGlobal: func(tmpDir string) {
|
||||
agentCVMClientKey = filepath.Join(tmpDir, "client.key")
|
||||
agentCVMClientCrt = filepath.Join(tmpDir, "nonexistent.crt")
|
||||
agentCVMServerCA = ""
|
||||
setupCLI: func(tmpDir string, c *CLI) {
|
||||
c.AgentVM.CVMClientKey = filepath.Join(tmpDir, "client.key")
|
||||
c.AgentVM.CVMClientCrt = filepath.Join(tmpDir, "nonexistent.crt")
|
||||
c.AgentVM.CVMServerCA = ""
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
@@ -479,10 +479,10 @@ func TestLoadCerts(t *testing.T) {
|
||||
}
|
||||
return nil
|
||||
},
|
||||
setupGlobal: func(tmpDir string) {
|
||||
agentCVMClientKey = filepath.Join(tmpDir, "client.key")
|
||||
agentCVMClientCrt = filepath.Join(tmpDir, "client.crt")
|
||||
agentCVMServerCA = filepath.Join(tmpDir, "nonexistent.ca")
|
||||
setupCLI: func(tmpDir string, c *CLI) {
|
||||
c.AgentVM.CVMClientKey = filepath.Join(tmpDir, "client.key")
|
||||
c.AgentVM.CVMClientCrt = filepath.Join(tmpDir, "client.crt")
|
||||
c.AgentVM.CVMServerCA = filepath.Join(tmpDir, "nonexistent.ca")
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
@@ -497,22 +497,10 @@ func TestLoadCerts(t *testing.T) {
|
||||
err = tt.setupFiles(tmpDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Store original global variables
|
||||
origClientKey := agentCVMClientKey
|
||||
origClientCrt := agentCVMClientCrt
|
||||
origServerCA := agentCVMServerCA
|
||||
c := &CLI{}
|
||||
tt.setupCLI(tmpDir, c)
|
||||
|
||||
// Setup global variables for test
|
||||
tt.setupGlobal(tmpDir)
|
||||
|
||||
// Restore original values after test
|
||||
defer func() {
|
||||
agentCVMClientKey = origClientKey
|
||||
agentCVMClientCrt = origClientCrt
|
||||
agentCVMServerCA = origServerCA
|
||||
}()
|
||||
|
||||
result, err := loadCerts()
|
||||
result, err := c.loadCerts()
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
@@ -592,7 +580,7 @@ func TestTTLHandling(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedTTL, ttl)
|
||||
assert.Equal(t, tt.expectedTTL, mockCLI.AgentVM.Ttl)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+6
-6
@@ -24,7 +24,7 @@ func (cli *CLI) NewResultsCmd() *cobra.Command {
|
||||
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)
|
||||
cli.printError(cmd, "Failed to connect to agent: %v ❌ ", cli.connectErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@ func (cli *CLI) NewResultsCmd() *cobra.Command {
|
||||
|
||||
privKeyFile, err := os.ReadFile(args[0])
|
||||
if err != nil {
|
||||
printError(cmd, "Error reading private key file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error reading private key file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
var outputPath string
|
||||
if outputDir != "" {
|
||||
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
||||
printError(cmd, "Error creating output directory: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error creating output directory: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
outputPath = filepath.Join(outputDir, filename)
|
||||
@@ -56,19 +56,19 @@ func (cli *CLI) NewResultsCmd() *cobra.Command {
|
||||
|
||||
privKey, err := decodeKey(pemBlock)
|
||||
if err != nil {
|
||||
printError(cmd, "Error decoding private key: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error decoding private key: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
resultFile, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
printError(cmd, "Error creating result file: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error creating result file: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
defer resultFile.Close()
|
||||
|
||||
if err = cli.agentSDK.Result(cmd.Context(), privKey, resultFile); err != nil {
|
||||
printError(cmd, "Error retrieving computation result: %v ❌ ", err)
|
||||
cli.printError(cmd, "Error retrieving computation result: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+28
-1
@@ -4,6 +4,7 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
@@ -15,7 +16,26 @@ import (
|
||||
"github.com/ultravioletrs/cocos/pkg/sdk"
|
||||
)
|
||||
|
||||
var Verbose bool
|
||||
type AgentVMConfig struct {
|
||||
CVMServerURL string
|
||||
CVMServerCA string
|
||||
CVMClientKey string
|
||||
CVMClientCrt string
|
||||
CVMCaURL string
|
||||
LogLevel string
|
||||
Ttl time.Duration
|
||||
}
|
||||
|
||||
type AWSConfig struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
EndpointURL string
|
||||
Region string
|
||||
}
|
||||
|
||||
type AttestationConfig struct {
|
||||
KbsParams string
|
||||
}
|
||||
|
||||
type CLI struct {
|
||||
agentSDK sdk.SDK
|
||||
@@ -25,6 +45,13 @@ type CLI struct {
|
||||
managerClient manager.ManagerServiceClient
|
||||
connectErr error
|
||||
measurement cmdconfig.MeasurementProvider
|
||||
Verbose bool
|
||||
IsManifest bool
|
||||
ToBase64 bool
|
||||
KeyType string
|
||||
AgentVM AgentVMConfig
|
||||
AWS AWSConfig
|
||||
Attestation AttestationConfig
|
||||
}
|
||||
|
||||
func New(agentConfig clients.AttestedClientConfig, managerConfig clients.StandardClientConfig, measurement cmdconfig.MeasurementProvider) *CLI {
|
||||
|
||||
+11
-5
@@ -17,8 +17,8 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/absmach/certs/sdk"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/absmach/supermq/pkg/prometheus"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/api"
|
||||
@@ -269,7 +269,7 @@ func main() {
|
||||
return mc.Process(ctx, cancel)
|
||||
})
|
||||
|
||||
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
|
||||
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc, ccPlatform)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get attestation: %s", err))
|
||||
exitCode = 1
|
||||
@@ -317,7 +317,7 @@ func newService(ctx context.Context, logger *slog.Logger, eventSvc events.Servic
|
||||
return svc
|
||||
}
|
||||
|
||||
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
|
||||
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service, ccPlatform attestation.PlatformType) ([]byte, string, error) {
|
||||
if certFilePath == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
@@ -328,6 +328,9 @@ func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Ser
|
||||
}
|
||||
|
||||
certPem, _ := pem.Decode(certFile)
|
||||
if certPem == nil {
|
||||
return nil, "", fmt.Errorf("failed to decode certificate PEM")
|
||||
}
|
||||
certx509, err := x509.ParseCertificate(certPem.Bytes)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
@@ -335,7 +338,7 @@ func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Ser
|
||||
|
||||
nonceSNP := sha512.Sum512(certFile)
|
||||
nonceVTPM := sha256.Sum256(certFile)
|
||||
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestation.SNPvTPM)
|
||||
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, ccPlatform)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -354,6 +357,9 @@ func azureAttestationFromCert(ctx context.Context, certFilePath string, svc agen
|
||||
}
|
||||
|
||||
certPem, _ := pem.Decode(certFile)
|
||||
if certPem == nil {
|
||||
return nil, "", fmt.Errorf("failed to decode certificate PEM")
|
||||
}
|
||||
certx509, err := x509.ParseCertificate(certPem.Bytes)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
|
||||
@@ -33,6 +33,12 @@ func (s *service) FetchRawEvidence(ctx context.Context, req *attestationpb.Attes
|
||||
var nonce [32]byte
|
||||
copy(nonce[:], req.Nonce)
|
||||
binaryReport, err = s.provider.Attestation(reportData[:], nonce[:])
|
||||
case attestationpb.PlatformType_PLATFORM_TYPE_AZURE:
|
||||
var reportData [64]byte
|
||||
copy(reportData[:], req.ReportData)
|
||||
var nonce [32]byte
|
||||
copy(nonce[:], req.Nonce)
|
||||
binaryReport, err = s.provider.Attestation(reportData[:], nonce[:])
|
||||
case attestationpb.PlatformType_PLATFORM_TYPE_UNSPECIFIED:
|
||||
// Generate sample attestation for testing in non-TEE environments
|
||||
// This uses the underlying provider (EmptyProvider or CC Attestation Agent)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/eat"
|
||||
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
|
||||
)
|
||||
|
||||
func newGPUCollector(cfg config) (attestationgpu.Collector, error) {
|
||||
if strings.TrimSpace(cfg.GPUHelperPath) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return attestationgpu.NewCommandCollector(cfg.GPUHelperPath, cfg.GPUHelperTimeout)
|
||||
}
|
||||
|
||||
func (s *service) claimOptions(ctx context.Context, req *attestationpb.AttestationRequest, platformType attestation.PlatformType) ([]eat.ClaimsOption, error) {
|
||||
var opts []eat.ClaimsOption
|
||||
|
||||
if s.gpuCollector != nil && shouldCollectGPU(platformType) {
|
||||
sessionNonce := requestNonce(req)
|
||||
gpuNonce := deriveComponentNonce(sessionNonce, "gpu")
|
||||
|
||||
evidence, err := s.gpuCollector.Collect(ctx, gpuNonce)
|
||||
if err != nil {
|
||||
// GPU evidence is opportunistic: if no supported CC-capable GPU is
|
||||
// attached, or the helper cannot collect evidence, we continue with
|
||||
// the root CPU/TEE attestation instead of failing the whole request.
|
||||
s.logger.Warn(fmt.Sprintf("[ATTESTATION-SERVICE] Skipping optional GPU evidence collection: %s", err))
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Collected GPU evidence: format=%s bytes=%d",
|
||||
evidence.EvidenceFormat, len(evidence.RawEvidence)))
|
||||
|
||||
opts = append(opts, eat.WithGPU(&eat.GPUExtensions{
|
||||
Vendor: evidence.Vendor,
|
||||
EvidenceFormat: evidence.EvidenceFormat,
|
||||
Nonce: gpuNonce,
|
||||
EvidenceJSON: evidence.RawEvidence,
|
||||
}))
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func shouldCollectGPU(platformType attestation.PlatformType) bool {
|
||||
switch platformType {
|
||||
case attestation.SNP, attestation.SNPvTPM, attestation.TDX, attestation.Azure:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func requestNonce(req *attestationpb.AttestationRequest) []byte {
|
||||
if len(req.Nonce) > 0 {
|
||||
return append([]byte(nil), req.Nonce...)
|
||||
}
|
||||
|
||||
return append([]byte(nil), req.ReportData...)
|
||||
}
|
||||
|
||||
func deriveComponentNonce(sessionNonce []byte, component string) []byte {
|
||||
digest := sha256.Sum256(append(append([]byte(nil), sessionNonce...), []byte(":"+component)...))
|
||||
return digest[:]
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
|
||||
)
|
||||
|
||||
func TestRequestNonce(t *testing.T) {
|
||||
req := &attestationpb.AttestationRequest{
|
||||
ReportData: []byte("report"),
|
||||
Nonce: []byte("nonce"),
|
||||
}
|
||||
|
||||
assert.Equal(t, []byte("nonce"), requestNonce(req))
|
||||
|
||||
req.Nonce = nil
|
||||
assert.Equal(t, []byte("report"), requestNonce(req))
|
||||
}
|
||||
|
||||
func TestDeriveComponentNonce(t *testing.T) {
|
||||
sessionNonce := []byte("session-nonce")
|
||||
|
||||
gpuNonce := deriveComponentNonce(sessionNonce, "gpu")
|
||||
gpuNonceAgain := deriveComponentNonce(sessionNonce, "gpu")
|
||||
teeNonce := deriveComponentNonce(sessionNonce, "tee")
|
||||
|
||||
assert.Len(t, gpuNonce, 32)
|
||||
assert.Equal(t, gpuNonce, gpuNonceAgain)
|
||||
assert.NotEqual(t, gpuNonce, teeNonce)
|
||||
}
|
||||
|
||||
func TestShouldCollectGPU(t *testing.T) {
|
||||
assert.True(t, shouldCollectGPU(attestation.SNP))
|
||||
assert.True(t, shouldCollectGPU(attestation.SNPvTPM))
|
||||
assert.True(t, shouldCollectGPU(attestation.TDX))
|
||||
assert.False(t, shouldCollectGPU(attestation.VTPM))
|
||||
assert.False(t, shouldCollectGPU(attestation.NoCC))
|
||||
}
|
||||
|
||||
func TestNewGPUCollector(t *testing.T) {
|
||||
collector, err := newGPUCollector(config{})
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, collector)
|
||||
|
||||
collector, err = newGPUCollector(config{
|
||||
GPUHelperPath: "/tmp/helper",
|
||||
GPUHelperTimeout: 0,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, collector)
|
||||
}
|
||||
|
||||
func TestClaimOptions_SkipsOptionalGPUFailure(t *testing.T) {
|
||||
svc := &service{
|
||||
logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
|
||||
gpuCollector: failingCollector{},
|
||||
}
|
||||
|
||||
req := &attestationpb.AttestationRequest{
|
||||
ReportData: []byte("report-data"),
|
||||
Nonce: []byte("nonce-data"),
|
||||
}
|
||||
|
||||
opts, err := svc.claimOptions(context.Background(), req, attestation.TDX)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, opts)
|
||||
}
|
||||
|
||||
type failingCollector struct{}
|
||||
|
||||
func (failingCollector) Collect(context.Context, []byte) (*attestationgpu.Evidence, error) {
|
||||
return nil, assert.AnError
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
logpb "github.com/ultravioletrs/cocos/agent/log"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/ccaa"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/eat"
|
||||
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/tdx"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
|
||||
logclient "github.com/ultravioletrs/cocos/pkg/clients/grpc/log"
|
||||
@@ -35,16 +37,18 @@ const (
|
||||
)
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"ATTESTATION_LOG_LEVEL" envDefault:"debug"`
|
||||
Vmpl int `env:"ATTESTATION_VMPL" envDefault:"2"`
|
||||
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
|
||||
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
|
||||
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
|
||||
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
|
||||
EATFormat string `env:"ATTESTATION_EAT_FORMAT" envDefault:"CBOR"` // JWT or CBOR
|
||||
EATIssuer string `env:"ATTESTATION_EAT_ISSUER" envDefault:"cocos-attestation-service"`
|
||||
UseCCAttestationAgent bool `env:"USE_CC_ATTESTATION_AGENT" envDefault:"false"`
|
||||
CCAgentAddress string `env:"CC_AGENT_ADDRESS" envDefault:"127.0.0.1:50002"`
|
||||
LogLevel string `env:"ATTESTATION_LOG_LEVEL" envDefault:"debug"`
|
||||
Vmpl int `env:"ATTESTATION_VMPL" envDefault:"2"`
|
||||
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
|
||||
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
|
||||
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
|
||||
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
|
||||
EATFormat string `env:"ATTESTATION_EAT_FORMAT" envDefault:"CBOR"` // JWT or CBOR
|
||||
EATIssuer string `env:"ATTESTATION_EAT_ISSUER" envDefault:"cocos-attestation-service"`
|
||||
UseCCAttestationAgent bool `env:"USE_CC_ATTESTATION_AGENT" envDefault:"false"`
|
||||
CCAgentAddress string `env:"CC_AGENT_ADDRESS" envDefault:"127.0.0.1:50002"`
|
||||
GPUHelperPath string `env:"ATTESTATION_GPU_HELPER_PATH" envDefault:""`
|
||||
GPUHelperTimeout time.Duration `env:"ATTESTATION_GPU_HELPER_TIMEOUT" envDefault:"30s"`
|
||||
|
||||
// Future KBS Integration Configuration
|
||||
// When KBS support is added, these fields will enable:
|
||||
@@ -152,8 +156,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
isDirectProvider := false
|
||||
// Fallback to direct providers if CC AA not configured or unavailable
|
||||
if provider == nil {
|
||||
isDirectProvider = true
|
||||
switch ccPlatform {
|
||||
case attestation.SNP:
|
||||
provider = vtpm.NewProvider(false, uint(cfg.Vmpl))
|
||||
@@ -184,7 +190,7 @@ func main() {
|
||||
logger.Error("[ATTESTATION-SERVICE] No provider configured!")
|
||||
}
|
||||
|
||||
if ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM {
|
||||
if (ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM) && isDirectProvider {
|
||||
if err := vtpm.FetchSEVCertificates(uint(cfg.Vmpl)); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to fetch certificates: %s", err))
|
||||
exitCode = 1
|
||||
@@ -229,13 +235,24 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gpuCollector, err := newGPUCollector(cfg)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to configure GPU attestation collector: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
if gpuCollector != nil {
|
||||
logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] GPU evidence collection enabled via helper %s", cfg.GPUHelperPath))
|
||||
}
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
svc := &service{
|
||||
provider: provider,
|
||||
logger: logger,
|
||||
signingKey: signingKey,
|
||||
eatFormat: cfg.EATFormat,
|
||||
eatIssuer: cfg.EATIssuer,
|
||||
provider: provider,
|
||||
logger: logger,
|
||||
signingKey: signingKey,
|
||||
eatFormat: cfg.EATFormat,
|
||||
eatIssuer: cfg.EATIssuer,
|
||||
gpuCollector: gpuCollector,
|
||||
}
|
||||
attestationpb.RegisterAttestationServiceServer(grpcServer, svc)
|
||||
|
||||
@@ -267,11 +284,12 @@ func main() {
|
||||
|
||||
type service struct {
|
||||
attestationpb.UnimplementedAttestationServiceServer
|
||||
provider attestation.Provider
|
||||
logger *slog.Logger
|
||||
signingKey *ecdsa.PrivateKey
|
||||
eatFormat string
|
||||
eatIssuer string
|
||||
provider attestation.Provider
|
||||
logger *slog.Logger
|
||||
signingKey *ecdsa.PrivateKey
|
||||
eatFormat string
|
||||
eatIssuer string
|
||||
gpuCollector attestationgpu.Collector
|
||||
}
|
||||
|
||||
func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.AttestationRequest) (*attestationpb.AttestationResponse, error) {
|
||||
@@ -302,6 +320,13 @@ func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.Attes
|
||||
copy(nonce[:], req.Nonce)
|
||||
binaryReport, err = s.provider.Attestation(reportData[:], nonce[:])
|
||||
platformType = attestation.SNPvTPM
|
||||
case attestationpb.PlatformType_PLATFORM_TYPE_AZURE:
|
||||
var reportData [64]byte
|
||||
copy(reportData[:], req.ReportData)
|
||||
var nonce [32]byte
|
||||
copy(nonce[:], req.Nonce)
|
||||
binaryReport, err = s.provider.Attestation(reportData[:], nonce[:])
|
||||
platformType = attestation.Azure
|
||||
case attestationpb.PlatformType_PLATFORM_TYPE_UNSPECIFIED:
|
||||
// Generate sample attestation for testing in non-TEE environments
|
||||
s.logger.Warn("generating sample attestation for PLATFORM_TYPE_UNSPECIFIED - this should only be used for testing")
|
||||
@@ -330,12 +355,14 @@ func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.Attes
|
||||
}
|
||||
|
||||
// Create EAT claims from binary report
|
||||
nonce := req.ReportData
|
||||
if len(req.Nonce) > 0 {
|
||||
nonce = req.Nonce
|
||||
nonce := requestNonce(req)
|
||||
|
||||
claimOpts, err := s.claimOptions(ctx, req, platformType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claims, err := eat.NewEATClaims(binaryReport, nonce, platformType)
|
||||
claims, err := eat.NewEATClaims(binaryReport, nonce, platformType, claimOpts...)
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to create EAT claims: %s", err))
|
||||
return nil, fmt.Errorf("failed to create EAT claims: %w", err)
|
||||
|
||||
+2
-2
@@ -122,7 +122,7 @@ func main() {
|
||||
defer cliSVC.Close()
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&cli.Verbose, "verbose", "v", false, "Enable verbose output")
|
||||
rootCmd.PersistentFlags().BoolVarP(&cliSVC.Verbose, "verbose", "v", false, "Enable verbose output")
|
||||
|
||||
keysCmd := cliSVC.NewKeysCmd()
|
||||
attestationCmd := cliSVC.NewAttestationCmd()
|
||||
@@ -151,7 +151,7 @@ func main() {
|
||||
|
||||
// Flags
|
||||
keysCmd.PersistentFlags().StringVarP(
|
||||
&cli.KeyType,
|
||||
&cliSVC.KeyType,
|
||||
"key-type",
|
||||
"k",
|
||||
"rsa",
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
logpb "github.com/ultravioletrs/cocos/agent/log"
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
pb "github.com/ultravioletrs/cocos/agent/log"
|
||||
|
||||
+7
-7
@@ -12,13 +12,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/absmach/supermq/pkg/jaeger"
|
||||
"github.com/absmach/supermq/pkg/prometheus"
|
||||
smqserver "github.com/absmach/supermq/pkg/server"
|
||||
grpcserver "github.com/absmach/supermq/pkg/server/grpc"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/jaeger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
smqserver "github.com/absmach/magistrala/pkg/server"
|
||||
grpcserver "github.com/absmach/magistrala/pkg/server/grpc"
|
||||
httpserver "github.com/absmach/magistrala/pkg/server/http"
|
||||
"github.com/absmach/magistrala/pkg/uuid"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
|
||||
@@ -4,48 +4,51 @@ go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/caarlos0/env/v11 v11.4.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/go-sev-guest v0.14.1
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/virtee/sev-snp-measure-go v0.0.0-20240530153610-e6e8dc9b6877
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0
|
||||
go.opentelemetry.io/otel/trace v1.41.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/sync v0.19.0
|
||||
google.golang.org/grpc v1.79.1
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/sync v0.20.0
|
||||
google.golang.org/grpc v1.80.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.57.2
|
||||
github.com/absmach/supermq v0.19.0
|
||||
cloud.google.com/go/storage v1.62.3
|
||||
github.com/absmach/magistrala v0.20.0
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/fxamacker/cbor/v2 v2.9.0
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-jose/go-jose/v4 v4.1.3
|
||||
github.com/go-jose/go-jose/v4 v4.1.4
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/gce-tcb-verifier v0.3.1
|
||||
github.com/veraison/corim v1.1.2
|
||||
github.com/veraison/go-cose v1.3.0
|
||||
github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca
|
||||
google.golang.org/api v0.274.0
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go v0.121.6 // indirect
|
||||
cloud.google.com/go/auth v0.16.5 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.19.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
cloud.google.com/go/iam v1.7.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
@@ -53,7 +56,7 @@ require (
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
@@ -65,8 +68,8 @@ require (
|
||||
github.com/google/go-attestation v0.5.1 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.21.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
@@ -80,33 +83,31 @@ require (
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff // indirect
|
||||
github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 // indirect
|
||||
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/api v0.247.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/absmach/certs v0.18.2
|
||||
github.com/absmach/certs v0.18.5
|
||||
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
|
||||
@@ -117,11 +118,11 @@ require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc // indirect
|
||||
github.com/google/go-tpm v0.9.6
|
||||
github.com/google/go-tpm-tools v0.4.7
|
||||
github.com/google/go-tpm v0.9.8
|
||||
github.com/google/go-tpm-tools v0.4.4
|
||||
github.com/google/logger v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
@@ -130,15 +131,15 @@ require (
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/term v0.40.0
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
|
||||
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
|
||||
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
|
||||
cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.19.0 h1:DGYwtbcsGsT1ywuxsIoWi1u/vlks0moIblQHgSDgQkQ=
|
||||
cloud.google.com/go/auth v0.19.0/go.mod h1:2Aph7BT2KnaSFOM0JDPyiYgNh6PL9vGMiP8CUIXZ+IY=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
|
||||
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
|
||||
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
|
||||
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
|
||||
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
|
||||
cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4=
|
||||
cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk=
|
||||
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
|
||||
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
|
||||
cloud.google.com/go/iam v1.7.0 h1:JD3zh0C6LHl16aCn5Akff0+GELdp1+4hmh6ndoFLl8U=
|
||||
cloud.google.com/go/iam v1.7.0/go.mod h1:tetWZW1PD/m6vcuY2Zj/aU0eCHNPuxedbnbRTyKXvdY=
|
||||
cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA=
|
||||
cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak=
|
||||
cloud.google.com/go/longrunning v0.9.0 h1:0EzbDEGsAvOZNbqXopgniY0w0a1phvu5IdUFq8grmqY=
|
||||
cloud.google.com/go/longrunning v0.9.0/go.mod h1:pkTz846W7bF4o2SzdWJ40Hu0Re+UoNT6Q5t+igIcb8E=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/storage v1.62.3 h1:SZq1t23NCI+e96dH77Dg3PEfsNNEjqO8zE5AnD8gVD0=
|
||||
cloud.google.com/go/storage v1.62.3/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA=
|
||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/absmach/certs v0.18.2 h1:s6KKL3/KfDZ6z0IxvNCksIOUwRnEgQyCpeAonuR15No=
|
||||
github.com/absmach/certs v0.18.2/go.mod h1:scqVZsmW2xPScnpMTtE70oN6cn0LLjFcJVPi4JKZ4+E=
|
||||
github.com/absmach/supermq v0.19.0 h1:sbqfzmSiMp9GEaCWgpREiLC0tFsSntgLIyAaZs7SnRY=
|
||||
github.com/absmach/supermq v0.19.0/go.mod h1:SG2yIzlJmc26ZjDVSkoapc6HZ6W13SUsaN3sAErfgC4=
|
||||
github.com/absmach/certs v0.18.5 h1:eYlvitou+LoDtt7ETVLTp6d/1xCejGL3EmVOg+rHGTU=
|
||||
github.com/absmach/certs v0.18.5/go.mod h1:31dtVe1VYF16W+IvjAE/uPAIz4f3uLHgh+moBezjqIc=
|
||||
github.com/absmach/magistrala v0.20.0 h1:3AQ0C2AMoOCc1UuJLhPNJLMrNRLZoN0ibSOERqEkM98=
|
||||
github.com/absmach/magistrala v0.20.0/go.mod h1:lnuO4fSngMiRYyNYL4yz5UP8DX3bbXRm87b2KHFGwJU=
|
||||
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4 h1:533pRc6R7perWDqJuZq+ofBQfYfmyj7n49V4LFY4zpo=
|
||||
github.com/absmach/supermq v0.19.2-0.20260317185610-fade98b84ee4/go.mod h1:xDAX/O3VcOsHWCx2fk85VD7FI17hAUOvoOhho7DA7g0=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
||||
@@ -58,7 +60,6 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
@@ -67,8 +68,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff h1:V6A5kD0+c1Qg4X72Lg+zxhCZk+par436sQdgLvMCBBc=
|
||||
@@ -81,10 +82,12 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
@@ -95,8 +98,8 @@ github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs
|
||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
|
||||
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
|
||||
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
|
||||
@@ -130,16 +133,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc h1:SG12DWUUM5igxm+//YX5Yq4vhdoRnOG9HkCodkOn+YU=
|
||||
github.com/google/go-configfs-tsm v0.3.3-0.20240919001351-b4b5b84fdcbc/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
|
||||
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba h1:05m5+kgZjxYUZrx3bZfkKHl6wkch+Khao6N21rFHInk=
|
||||
github.com/google/go-eventlog v0.0.2-0.20241003021507-01bb555f7cba/go.mod h1:7huE5P8w2NTObSwSJjboHmB7ioBNblkijdzoVa2skfQ=
|
||||
github.com/google/go-sev-guest v0.14.1 h1:j/DXy9jk1qSW/dEV9vDiQnhAVFD1zqnWNVu6p1J0Jgo=
|
||||
github.com/google/go-sev-guest v0.14.1/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760=
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843 h1:+MoPobRN9HrDhGyn6HnF5NYo4uMBKaiFqAtf/D/OB4A=
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843/go.mod h1:g/n8sKITIT9xRivBUbizo34DTsUm2nN2uU3A662h09g=
|
||||
github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA=
|
||||
github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
|
||||
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4 h1:OX2Mksz5ZHxawvZskqYX18Xy/q292EoiyUAT4WXg/gU=
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4/go.mod h1:uHy3VaNXNXhl0fiPxKqTxieeouqQmW6A0EfLcaeCYBk=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
||||
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
|
||||
@@ -151,30 +152,30 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI=
|
||||
github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -203,16 +204,16 @@ github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY=
|
||||
@@ -243,8 +244,8 @@ github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTC
|
||||
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
|
||||
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
|
||||
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -255,7 +256,6 @@ github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xI
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -276,28 +276,28 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0 h1:PnV4kVnw0zOmwwFkAzCN5O07fw1YOIQor120zrh0AVo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.66.0/go.mod h1:ofAwF4uinaf8SXdVzzbL4OsxJ3VfeEg3f/F6CeF49/Y=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8=
|
||||
go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
@@ -308,8 +308,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -317,15 +317,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -336,42 +336,42 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
|
||||
google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.274.0 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA=
|
||||
google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/agent/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/attestation-service/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/cc-attestation-agent/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/coco-keyprovider/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/wasmedge/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/log-forwarder/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/computation-runner/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/egress-proxy/Config.in"
|
||||
source "$BR2_EXTERNAL_COCOS_PATH/package/ingress-proxy/Config.in"
|
||||
@@ -0,0 +1,214 @@
|
||||
# Disk Image Workflow
|
||||
|
||||
This directory is the Buildroot external tree for the current Cocos disk test
|
||||
VM image and its runtime configuration.
|
||||
|
||||
## Layout
|
||||
|
||||
- [configs/cocos_defconfig](./configs/cocos_defconfig):
|
||||
Buildroot configuration for the bootable image.
|
||||
- [board/rootfs-overlay/init](./board/rootfs-overlay/init):
|
||||
early initramfs script that provisions `/cocos`, mounts the real root, and
|
||||
switches into the installed system.
|
||||
- [board/cocos/genimage.cfg](./board/cocos/genimage.cfg):
|
||||
GPT disk layout for the final `disk.img`.
|
||||
- [board/cocos/post-image.sh](./board/cocos/post-image.sh):
|
||||
builds the minimal initramfs, stages EFI files, signs boot artifacts, and
|
||||
assembles `disk.img`.
|
||||
- [external.desc](./external.desc): Buildroot external tree descriptor.
|
||||
- [external.mk](./external.mk): includes package makefiles from `package/*`.
|
||||
|
||||
## Current Buildroot Image
|
||||
|
||||
The current Buildroot flow produces a bootable GPT disk image:
|
||||
|
||||
- `efi` partition: FAT EFI system partition with GRUB, kernel, and initramfs
|
||||
- `root` partition: ext4 root filesystem protected by dm-verity
|
||||
- `verity` partition: dm-verity hash tree for the root filesystem
|
||||
- `cocos` partition: blank partition provisioned at boot as an encrypted ext4
|
||||
filesystem mounted at `/cocos`
|
||||
|
||||
The final image is written to:
|
||||
|
||||
```bash
|
||||
output/images/disk.img
|
||||
```
|
||||
|
||||
The root filesystem image is also available separately as:
|
||||
|
||||
```bash
|
||||
output/images/rootfs.ext4
|
||||
```
|
||||
|
||||
## Current Boot Flow
|
||||
|
||||
At boot, GRUB loads:
|
||||
|
||||
- `bzImage`
|
||||
- `initrd.cpio.gz`
|
||||
|
||||
The initramfs script in
|
||||
[board/rootfs-overlay/init](./board/rootfs-overlay/init)
|
||||
then:
|
||||
|
||||
1. mounts `/proc`, `/sys`, `devtmpfs`, and `devpts`
|
||||
2. assumes the boot disk is `/dev/sda`
|
||||
3. opens a dm-verity mapping for the root filesystem using:
|
||||
- `/dev/sda2` as the data partition
|
||||
- `/dev/sda3` as the verity hash partition
|
||||
- `roothash=` from the kernel command line
|
||||
4. mounts `/dev/mapper/root_verity` read-only at `/root`
|
||||
5. generates a fresh ephemeral key
|
||||
6. formats `/dev/sda4` as LUKS2
|
||||
7. opens it as `/dev/mapper/cocos_crypt`
|
||||
8. formats that mapper as ext4 and mounts it at `/root/cocos`
|
||||
9. creates working directories on `/cocos`, including:
|
||||
- `/cocos/.cache/oci`
|
||||
- `/cocos/datasets`
|
||||
- `/cocos/docker`
|
||||
- `/cocos/cocos_init`
|
||||
10. mounts `tmpfs` on `/tmp` and `/var` because the root filesystem is
|
||||
intentionally read-only
|
||||
11. bind-mounts `/cocos/docker` onto `/var/lib/docker`
|
||||
12. bind-mounts `/cocos/cocos_init` onto `/cocos_init`
|
||||
13. rewrites `/etc/fstab` in the mounted root to describe the live runtime
|
||||
14. preserves or adds 9P mounts for:
|
||||
- `certs_share` -> `/etc/certs`
|
||||
- `env_share` -> `/etc/cocos`
|
||||
15. securely wipes the temporary LUKS key file
|
||||
16. runs `switch_root /root /sbin/init`
|
||||
|
||||
Important details:
|
||||
|
||||
- the root filesystem is verified through dm-verity before it is mounted
|
||||
- `/cocos` is encrypted with an ephemeral per-boot key
|
||||
- that key is not persisted, so `/cocos` is provisioned fresh on each boot
|
||||
|
||||
## Runtime Filesystem Model
|
||||
|
||||
The running system is split into:
|
||||
|
||||
- read-only root on `/`
|
||||
- encrypted writable storage on `/cocos`
|
||||
- `tmpfs` on `/tmp`
|
||||
- `tmpfs` on `/var`
|
||||
|
||||
Service state that must survive within a boot session is redirected away from
|
||||
the read-only root:
|
||||
|
||||
- Docker data lives on `/cocos/docker`
|
||||
- agent setup scripts work through `/cocos_init`, which is backed by
|
||||
`/cocos/cocos_init`
|
||||
- algorithm datasets and results live under `/cocos`
|
||||
|
||||
This means services can use `/cocos` like a regular directory tree after boot,
|
||||
even though it is backed by an encrypted mapper created in early userspace.
|
||||
|
||||
## systemd Runtime Expectations
|
||||
|
||||
Several services depend on files mounted from 9P shares under `/etc/certs` and
|
||||
`/etc/cocos`. To avoid boot-order races, the rootfs overlay includes systemd
|
||||
drop-ins under:
|
||||
|
||||
```bash
|
||||
board/rootfs-overlay/usr/lib/systemd/system/*service.d/
|
||||
```
|
||||
|
||||
These drop-ins require the relevant mount points before starting services such
|
||||
as:
|
||||
|
||||
- `egress-proxy.service`
|
||||
- `log-forwarder.service`
|
||||
- `computation-runner.service`
|
||||
- `cocos-agent.service`
|
||||
|
||||
The overlay also ships tmpfiles rules in
|
||||
[board/rootfs-overlay/usr/lib/tmpfiles.d/cocos.conf](./board/rootfs-overlay/usr/lib/tmpfiles.d/cocos.conf)
|
||||
to create:
|
||||
|
||||
- `/var/log/cocos`
|
||||
- `/run/cocos`
|
||||
|
||||
## Agent Packaging In Buildroot
|
||||
|
||||
The Buildroot `agent` package is wired to build the binary from the local Cocos
|
||||
checkout, not only from a downloaded release snapshot. The package definition is
|
||||
in [package/agent/agent.mk](./package/agent/agent.mk).
|
||||
|
||||
That package currently:
|
||||
|
||||
- builds `cocos-agent` from the local source tree
|
||||
- installs the local
|
||||
[cocos-agent.service](../../init/systemd/cocos-agent.service)
|
||||
- installs the local
|
||||
[agent_setup.sh](../../init/systemd/agent_setup.sh)
|
||||
- installs the local
|
||||
[agent_start_script.sh](../../init/systemd/agent_start_script.sh)
|
||||
|
||||
So changes under:
|
||||
|
||||
- `cocos/agent/...`
|
||||
- `cocos/init/systemd/...`
|
||||
|
||||
are intended to be picked up by the next Buildroot rebuild.
|
||||
|
||||
## Buildroot Packages And Tools
|
||||
|
||||
The current `cocos_defconfig` includes the components needed by the boot flow
|
||||
and runtime image, including:
|
||||
|
||||
- systemd
|
||||
- DHCP client
|
||||
- `cryptsetup`
|
||||
- `eudev`
|
||||
- `e2fsprogs`
|
||||
- Docker, containerd, and runc
|
||||
- `skopeo`
|
||||
- TPM2 tools
|
||||
- 9P filesystem support
|
||||
- GRUB2 EFI boot support
|
||||
- host `genimage`
|
||||
|
||||
The initramfs built in `post-image.sh` is intentionally minimal and contains
|
||||
only the binaries needed for early boot, dm-verity root verification, and
|
||||
`/cocos` provisioning.
|
||||
|
||||
## Secure Boot Notes
|
||||
|
||||
During `post-image.sh`:
|
||||
|
||||
- GRUB is rebuilt with `--disable-shim-lock`
|
||||
- `bootx64.efi` and `bzImage` are signed with the configured Secure Boot keys
|
||||
when those keys are present
|
||||
|
||||
This flow is designed for booting directly through OVMF with your own enrolled
|
||||
keys. It does not currently rely on booting through `shim`.
|
||||
|
||||
## Rebuilding
|
||||
|
||||
This directory is meant to be used as a Buildroot external tree. From this
|
||||
directory, configure a Buildroot checkout with:
|
||||
|
||||
```bash
|
||||
make -C /path/to/buildroot BR2_EXTERNAL=$PWD cocos_defconfig
|
||||
```
|
||||
|
||||
Then build with:
|
||||
|
||||
```bash
|
||||
make -C /path/to/buildroot BR2_EXTERNAL=$PWD -j$(nproc)
|
||||
```
|
||||
|
||||
The resulting boot image is:
|
||||
|
||||
```bash
|
||||
/path/to/buildroot/output/images/disk.img
|
||||
```
|
||||
|
||||
Additional generated artifacts include:
|
||||
|
||||
```bash
|
||||
/path/to/buildroot/output/images/rootfs.ext4
|
||||
/path/to/buildroot/output/images/rootfs.verity
|
||||
/path/to/buildroot/output/images/rootfs.roothash
|
||||
```
|
||||
@@ -0,0 +1,42 @@
|
||||
image efi-part.vfat {
|
||||
vfat {
|
||||
file EFI {
|
||||
image = "efi-part/EFI"
|
||||
}
|
||||
file bzImage {
|
||||
image = "efi-part/bzImage"
|
||||
}
|
||||
file initrd.cpio.gz {
|
||||
image = "efi-part/initrd.cpio.gz"
|
||||
}
|
||||
}
|
||||
size = 256M
|
||||
}
|
||||
|
||||
image disk.img {
|
||||
hdimage {
|
||||
partition-table-type = "gpt"
|
||||
}
|
||||
|
||||
partition efi {
|
||||
image = "efi-part.vfat"
|
||||
partition-type-uuid = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
|
||||
offset = 1M
|
||||
bootable = true
|
||||
}
|
||||
|
||||
partition root {
|
||||
image = "rootfs.ext4"
|
||||
partition-type-uuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
|
||||
}
|
||||
|
||||
partition verity {
|
||||
image = "rootfs.verity"
|
||||
partition-type-uuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
|
||||
}
|
||||
|
||||
partition cocos {
|
||||
size = "20480M"
|
||||
partition-type-uuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
###
|
||||
# Architecture / base
|
||||
###
|
||||
CONFIG_SYSVIPC=y
|
||||
CONFIG_SMP=y
|
||||
CONFIG_EXPERT=y
|
||||
CONFIG_LOCALVERSION_AUTO=n
|
||||
|
||||
###
|
||||
# Modules
|
||||
###
|
||||
CONFIG_MODULES=y
|
||||
CONFIG_MODULE_UNLOAD=y
|
||||
|
||||
###
|
||||
# Virtualization
|
||||
###
|
||||
CONFIG_HYPERVISOR_GUEST=y
|
||||
CONFIG_PARAVIRT=y
|
||||
CONFIG_VIRTUALIZATION=y
|
||||
CONFIG_KVM=y
|
||||
CONFIG_KVM_SW_PROTECTED_VM=y
|
||||
CONFIG_KVM_INTEL=y
|
||||
CONFIG_VIRT_DRIVERS=y
|
||||
|
||||
###
|
||||
# Cgroups — base + Docker/container subsystems
|
||||
###
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_CPUACCT=y
|
||||
CONFIG_CGROUP_DEVICE=y
|
||||
CONFIG_CGROUP_FREEZER=y
|
||||
CONFIG_CGROUP_MISC=y
|
||||
CONFIG_CGROUP_PIDS=y
|
||||
CONFIG_CGROUP_BPF=y
|
||||
CONFIG_CGROUP_NET_PRIO=y
|
||||
CONFIG_CGROUP_NET_CLASSID=y
|
||||
CONFIG_CPUSETS=y
|
||||
CONFIG_MEMCG=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
|
||||
###
|
||||
# Namespaces — required by containerd / runc
|
||||
###
|
||||
CONFIG_NAMESPACES=y
|
||||
CONFIG_UTS_NS=y
|
||||
CONFIG_IPC_NS=y
|
||||
CONFIG_USER_NS=y
|
||||
CONFIG_PID_NS=y
|
||||
CONFIG_NET_NS=y
|
||||
|
||||
###
|
||||
# PCI
|
||||
###
|
||||
CONFIG_PCI=y
|
||||
CONFIG_PCI_MSI=y
|
||||
CONFIG_IRQ_REMAP=y
|
||||
|
||||
###
|
||||
# Initramfs
|
||||
###
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_RD_GZIP=y
|
||||
|
||||
###
|
||||
# Block devices
|
||||
###
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
CONFIG_BLK_DEV_SD=y
|
||||
CONFIG_SCSI_VIRTIO=y
|
||||
CONFIG_ATA=y
|
||||
CONFIG_ATA_PIIX=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
|
||||
# Loop device (used by containerd image mounts)
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_LOOP_MIN_COUNT=8
|
||||
|
||||
###
|
||||
# Device mapper — FDE, dm-verity, dm-crypt, dm-integrity
|
||||
# These must be built-in (y) because they are needed before the
|
||||
# rootfs is mounted, during the initramfs FDE init stage.
|
||||
###
|
||||
CONFIG_MD=y
|
||||
CONFIG_BLK_DEV_DM_BUILTIN=y
|
||||
CONFIG_BLK_DEV_DM=y
|
||||
CONFIG_DM_CRYPT=y
|
||||
CONFIG_DM_VERITY=y
|
||||
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG=y
|
||||
# CONFIG_DM_VERITY_FEC is not set
|
||||
CONFIG_DM_INTEGRITY=y
|
||||
CONFIG_DM_INIT=y
|
||||
|
||||
###
|
||||
# Networking — base
|
||||
###
|
||||
CONFIG_NET=y
|
||||
CONFIG_PACKET=y
|
||||
CONFIG_UNIX=y
|
||||
CONFIG_INET=y
|
||||
# CONFIG_WIRELESS is not set
|
||||
CONFIG_NETDEVICES=y
|
||||
CONFIG_VIRTIO_NET=y
|
||||
CONFIG_NE2K_PCI=y
|
||||
CONFIG_8139CP=y
|
||||
# CONFIG_WLAN is not set
|
||||
CONFIG_VSOCKETS=y
|
||||
CONFIG_VIRTIO_VSOCKETS=y
|
||||
|
||||
# Virtual Ethernet pairs and bridge (Docker networking)
|
||||
CONFIG_VETH=m
|
||||
CONFIG_BRIDGE=m
|
||||
CONFIG_BRIDGE_NETFILTER=m
|
||||
|
||||
###
|
||||
# Netfilter — Docker NAT, iptables, conntrack (modules, loaded on demand)
|
||||
###
|
||||
CONFIG_NETFILTER=y
|
||||
CONFIG_NETFILTER_ADVANCED=y
|
||||
CONFIG_NF_CONNTRACK=m
|
||||
CONFIG_NF_CONNTRACK_MARK=y
|
||||
CONFIG_NF_NAT=m
|
||||
CONFIG_NF_NAT_MASQUERADE=y
|
||||
CONFIG_NF_TABLES=y
|
||||
CONFIG_IP_NF_IPTABLES=m
|
||||
CONFIG_IP_NF_FILTER=m
|
||||
CONFIG_IP_NF_TARGET_MASQUERADE=m
|
||||
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
|
||||
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
|
||||
|
||||
###
|
||||
# BPF
|
||||
###
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
|
||||
###
|
||||
# Filesystems
|
||||
###
|
||||
CONFIG_EXT4_FS=y
|
||||
CONFIG_OVERLAY_FS=y
|
||||
CONFIG_AUTOFS4_FS=y
|
||||
CONFIG_TMPFS=y
|
||||
CONFIG_TMPFS_POSIX_ACL=y
|
||||
CONFIG_PROC_FS=y
|
||||
CONFIG_SYSFS=y
|
||||
|
||||
###
|
||||
# 9P filesystem (virtio shares for certs and env)
|
||||
###
|
||||
CONFIG_NET_9P=y
|
||||
CONFIG_NET_9P_VIRTIO=y
|
||||
CONFIG_9P_FS=y
|
||||
CONFIG_9P_FS_POSIX_ACL=y
|
||||
CONFIG_9P_FS_SECURITY=y
|
||||
|
||||
###
|
||||
# Virtio devices
|
||||
###
|
||||
CONFIG_VIRTIO_PCI=y
|
||||
CONFIG_VIRTIO_BALLOON=y
|
||||
CONFIG_VIRTIO_INPUT=y
|
||||
CONFIG_VIRTIO_CONSOLE=y
|
||||
CONFIG_VIRTIO_MMIO=y
|
||||
CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y
|
||||
CONFIG_HW_RANDOM_VIRTIO=m
|
||||
|
||||
###
|
||||
# Console / Input
|
||||
###
|
||||
CONFIG_INPUT_EVDEV=y
|
||||
CONFIG_SERIAL_8250=y
|
||||
CONFIG_SERIAL_8250_CONSOLE=y
|
||||
|
||||
###
|
||||
# Kernel features required by systemd
|
||||
###
|
||||
CONFIG_FHANDLE=y
|
||||
CONFIG_INOTIFY_USER=y
|
||||
CONFIG_SIGNALFD=y
|
||||
CONFIG_TIMERFD=y
|
||||
CONFIG_EPOLL=y
|
||||
CONFIG_POSIX_MQUEUE=y
|
||||
CONFIG_POSIX_MQUEUE_SYSCTL=y
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
|
||||
###
|
||||
# Security
|
||||
###
|
||||
CONFIG_SECCOMP=y
|
||||
CONFIG_SECCOMP_FILTER=y
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_SECURITYFS=y
|
||||
|
||||
###
|
||||
# EFI
|
||||
###
|
||||
CONFIG_EFI=y
|
||||
CONFIG_EFI_STUB=y
|
||||
|
||||
###
|
||||
# AMD SEV-SNP
|
||||
###
|
||||
CONFIG_AMD_MEM_ENCRYPT=y
|
||||
CONFIG_AMD_MEM_ENCRYPT_ACTIVE_BY_DEFAULT=n
|
||||
CONFIG_SEV_GUEST=y
|
||||
CONFIG_IOMMU_DEFAULT_PASSTHROUGH=n
|
||||
|
||||
###
|
||||
# Intel TDX
|
||||
###
|
||||
CONFIG_X86_X2APIC=y
|
||||
CONFIG_X86_CPUID=y
|
||||
CONFIG_X86_SGX=y
|
||||
CONFIG_X86_SGX_KVM=y
|
||||
CONFIG_INTEL_TDX_GUEST=y
|
||||
CONFIG_TDX_GUEST_DRIVER=y
|
||||
|
||||
###
|
||||
# Preemption (disabled for VM performance)
|
||||
###
|
||||
CONFIG_PREEMPT_COUNT=n
|
||||
CONFIG_PREEMPT=n
|
||||
CONFIG_PREEMPT_DYNAMIC=n
|
||||
CONFIG_DEBUG_PREEMPT=n
|
||||
|
||||
###
|
||||
# Key/signature management
|
||||
###
|
||||
CONFIG_SYSTEM_TRUSTED_KEYS=n
|
||||
CONFIG_SYSTEM_REVOCATION_KEYS=n
|
||||
CONFIG_MODULE_SIG_KEY=n
|
||||
CONFIG_KEYS=y
|
||||
CONFIG_ENCRYPTED_KEYS=y
|
||||
|
||||
###
|
||||
# Crypto — AES-GCM (LUKS2 cipher) + SHA-256 (dm-verity hash)
|
||||
###
|
||||
CONFIG_CRYPTO_AES=y
|
||||
CONFIG_CRYPTO_SHA256=y
|
||||
CONFIG_CRYPTO_GCM=y
|
||||
CONFIG_CRYPTO_GHASH=y
|
||||
CONFIG_CRYPTO_SEQIV=y
|
||||
CONFIG_CRYPTO_ECHAINIV=y
|
||||
CONFIG_CRYPTO_XTS=y
|
||||
CONFIG_CRYPTO_CBC=y
|
||||
CONFIG_CRYPTO_AUTHENC=y
|
||||
CONFIG_CRYPTO_ESSIV=y
|
||||
CONFIG_CRYPTO_USER_API=y
|
||||
CONFIG_CRYPTO_USER_API_HASH=y
|
||||
CONFIG_CRYPTO_USER_API_SKCIPHER=y
|
||||
CONFIG_CRYPTO_USER_API_AEAD=y
|
||||
CONFIG_CRYPTO_AES_NI_INTEL=m
|
||||
CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL=m
|
||||
|
||||
###
|
||||
# TPM
|
||||
###
|
||||
CONFIG_TCG_TPM=y
|
||||
CONFIG_TCG_TPM2_HMAC=y
|
||||
CONFIG_TCG_PLATFORM=y
|
||||
|
||||
###
|
||||
# IMA (Linux Integrity Measurement Architecture)
|
||||
###
|
||||
CONFIG_INTEGRITY=y
|
||||
CONFIG_INTEGRITY_SIGNATURE=y
|
||||
CONFIG_IMA=y
|
||||
CONFIG_IMA_MEASURE_PCR_IDX=10
|
||||
CONFIG_IMA_LSM_RULES=y
|
||||
CONFIG_IMA_APPRAISE=y
|
||||
CONFIG_IMA_DEFAULT_TEMPLATE="ima-ng"
|
||||
CONFIG_IMA_DEFAULT_HASH="sha256"
|
||||
|
||||
###
|
||||
# Disabled options
|
||||
###
|
||||
CONFIG_KSM=n
|
||||
CONFIG_EISA=n
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -u
|
||||
set -e
|
||||
|
||||
# Add a console on tty1
|
||||
if [ -e ${TARGET_DIR}/etc/inittab ]; then
|
||||
grep -qE '^tty1::' ${TARGET_DIR}/etc/inittab || \
|
||||
sed -i '/GENERIC_SERIAL/a\
|
||||
tty1::respawn:/sbin/getty -L tty1 0 vt100 # QEMU graphical window' ${TARGET_DIR}/etc/inittab
|
||||
fi
|
||||
Executable
+273
@@ -0,0 +1,273 @@
|
||||
#!/bin/bash
|
||||
|
||||
COCOS_BOARD_DIR="$(dirname "$0")"
|
||||
DEFCONFIG_NAME="$(basename "$2")"
|
||||
README_FILES="${COCOS_BOARD_DIR}/readme.txt"
|
||||
START_QEMU_SCRIPT="${BINARIES_DIR}/start-qemu.sh"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build a minimal FDE initramfs (rootfs.cpio.gz) containing only the tools
|
||||
# needed to mount the root partition read-only, provision LUKS2, and switch_root.
|
||||
# All other packages live
|
||||
# on the ext4 disk image and are available after switch_root.
|
||||
# ---------------------------------------------------------------------------
|
||||
echo "[post-image] Building minimal FDE initramfs..."
|
||||
|
||||
INITRAMFS_STAGE="${BUILD_DIR}/initramfs-staging"
|
||||
rm -rf "${INITRAMFS_STAGE}"
|
||||
|
||||
# Merged-usr layout: bin/sbin/lib/lib64 are symlinks into usr/, matching the
|
||||
# Buildroot target layout so that hardcoded ELF interpreter paths (ld-linux)
|
||||
# and the #!/bin/sh shebang both resolve correctly inside the initramfs.
|
||||
mkdir -p "${INITRAMFS_STAGE}/usr/bin" \
|
||||
"${INITRAMFS_STAGE}/usr/sbin" \
|
||||
"${INITRAMFS_STAGE}/usr/lib" \
|
||||
"${INITRAMFS_STAGE}/dev" \
|
||||
"${INITRAMFS_STAGE}/proc" \
|
||||
"${INITRAMFS_STAGE}/sys" \
|
||||
"${INITRAMFS_STAGE}/tmp" \
|
||||
"${INITRAMFS_STAGE}/run" \
|
||||
"${INITRAMFS_STAGE}/root" \
|
||||
"${INITRAMFS_STAGE}/etc/udev/rules.d"
|
||||
ln -s usr/bin "${INITRAMFS_STAGE}/bin"
|
||||
ln -s usr/sbin "${INITRAMFS_STAGE}/sbin"
|
||||
ln -s usr/lib "${INITRAMFS_STAGE}/lib"
|
||||
ln -s usr/lib "${INITRAMFS_STAGE}/lib64"
|
||||
|
||||
# init script (PID 1)
|
||||
install -m 0755 "${BR2_EXTERNAL_COCOS_PATH}/board/rootfs-overlay/init" \
|
||||
"${INITRAMFS_STAGE}/init"
|
||||
|
||||
# Binaries required by the init script
|
||||
FDE_BINS="
|
||||
bash
|
||||
cryptsetup
|
||||
veritysetup
|
||||
mkfs.ext4
|
||||
mount
|
||||
umount
|
||||
losetup
|
||||
switch_root
|
||||
dd
|
||||
shred
|
||||
tr
|
||||
cut
|
||||
grep
|
||||
awk
|
||||
cat
|
||||
ls
|
||||
cp
|
||||
mkdir
|
||||
readlink
|
||||
dirname
|
||||
lsblk
|
||||
udevadm
|
||||
blkid
|
||||
rm
|
||||
"
|
||||
|
||||
for BIN in ${FDE_BINS}; do
|
||||
SRC="$(find "${TARGET_DIR}/usr/bin" "${TARGET_DIR}/usr/sbin" \
|
||||
"${TARGET_DIR}/bin" "${TARGET_DIR}/sbin" \
|
||||
-name "${BIN}" \( -type f -o -type l \) 2>/dev/null | head -1)"
|
||||
if [ -n "${SRC}" ]; then
|
||||
cp -P "${SRC}" "${INITRAMFS_STAGE}/usr/bin/${BIN}"
|
||||
chmod 0755 "${INITRAMFS_STAGE}/usr/bin/${BIN}" 2>/dev/null || true
|
||||
# If this is a symlink, also copy the resolved target binary (e.g. busybox, coreutils, mke2fs)
|
||||
# so that other applet symlinks pointing to the same target also work at runtime.
|
||||
if [ -L "${SRC}" ]; then
|
||||
REAL_SRC="$(readlink -f "${SRC}")"
|
||||
REAL_NAME="$(basename "${REAL_SRC}")"
|
||||
if [ -f "${REAL_SRC}" ] && [ ! -e "${INITRAMFS_STAGE}/usr/bin/${REAL_NAME}" ]; then
|
||||
cp "${REAL_SRC}" "${INITRAMFS_STAGE}/usr/bin/${REAL_NAME}"
|
||||
chmod 0755 "${INITRAMFS_STAGE}/usr/bin/${REAL_NAME}" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "[post-image] WARNING: ${BIN} not found in target, skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
# sh symlink so #!/bin/sh in the init script resolves correctly
|
||||
ln -sf bash "${INITRAMFS_STAGE}/usr/bin/sh"
|
||||
|
||||
# Shared libraries from usr/lib (TARGET_DIR uses merged-usr so lib → usr/lib)
|
||||
# Skip large runtimes that are only needed on the real root.
|
||||
find "${TARGET_DIR}/usr/lib" \( \
|
||||
-path "*/python3*" -o \
|
||||
-path "*/gcc*" -o \
|
||||
-path "*/wasmedge*" \
|
||||
\) -prune -o \
|
||||
\( -name "*.so" -o -name "*.so.*" \) -print | while read -r LIB; do
|
||||
REL="${LIB#${TARGET_DIR}/usr/lib/}"
|
||||
DEST="${INITRAMFS_STAGE}/usr/lib/${REL}"
|
||||
mkdir -p "$(dirname "${DEST}")"
|
||||
cp -P "${LIB}" "${DEST}"
|
||||
done
|
||||
|
||||
# udev rules (needed for udevadm settle)
|
||||
if [ -d "${TARGET_DIR}/etc/udev" ]; then
|
||||
cp -a "${TARGET_DIR}/etc/udev/." "${INITRAMFS_STAGE}/etc/udev/"
|
||||
fi
|
||||
|
||||
# /dev seed nodes
|
||||
mknod -m 0600 "${INITRAMFS_STAGE}/dev/console" c 5 1 2>/dev/null || true
|
||||
mknod -m 0666 "${INITRAMFS_STAGE}/dev/null" c 1 3 2>/dev/null || true
|
||||
|
||||
echo "[post-image] Packing initramfs..."
|
||||
( cd "${INITRAMFS_STAGE}" && \
|
||||
find . | cpio --quiet -o -H newc -R 0:0 | gzip -9 \
|
||||
> "${BINARIES_DIR}/rootfs.cpio.gz" )
|
||||
echo "[post-image] rootfs.cpio.gz: $(du -sh "${BINARIES_DIR}/rootfs.cpio.gz" | cut -f1)"
|
||||
|
||||
ROOTFS_IMAGE="${BINARIES_DIR}/rootfs.ext4"
|
||||
VERITY_IMAGE="${BINARIES_DIR}/rootfs.verity"
|
||||
ROOT_HASH_FILE="${BINARIES_DIR}/rootfs.roothash"
|
||||
VERITYSETUP_BIN="${HOST_DIR}/bin/veritysetup"
|
||||
|
||||
if [ ! -x "${VERITYSETUP_BIN}" ]; then
|
||||
VERITYSETUP_BIN="${HOST_DIR}/sbin/veritysetup"
|
||||
fi
|
||||
|
||||
if [ ! -x "${VERITYSETUP_BIN}" ]; then
|
||||
echo "[post-image] FATAL: host veritysetup not found at ${VERITYSETUP_BIN}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[post-image] Building dm-verity hash image..."
|
||||
rm -f "${VERITY_IMAGE}" "${ROOT_HASH_FILE}"
|
||||
truncate -s 256M "${VERITY_IMAGE}"
|
||||
VERITY_FORMAT_OUTPUT="$("${VERITYSETUP_BIN}" format "${ROOTFS_IMAGE}" "${VERITY_IMAGE}")" || {
|
||||
echo "[post-image] FATAL: veritysetup format failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ROOT_HASH="$(printf '%s\n' "${VERITY_FORMAT_OUTPUT}" | awk -F': ' '/^Root hash:/ {print $2}' | tr -d '[:space:]')"
|
||||
if [ -z "${ROOT_HASH}" ]; then
|
||||
echo "[post-image] FATAL: failed to parse dm-verity root hash"
|
||||
printf '%s\n' "${VERITY_FORMAT_OUTPUT}"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "${ROOT_HASH}" > "${ROOT_HASH_FILE}"
|
||||
echo "[post-image] dm-verity root hash: ${ROOT_HASH}"
|
||||
|
||||
# Stage kernel and initramfs for the EFI partition.
|
||||
# Buildroot's GRUB2 package has already placed bootx64.efi at
|
||||
# ${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi; we add the kernel,
|
||||
# initramfs, and overwrite the default grub.cfg with our boot entry.
|
||||
echo "[post-image] Staging EFI partition files..."
|
||||
mkdir -p "${BINARIES_DIR}/efi-part/EFI/BOOT"
|
||||
cp "${BINARIES_DIR}/bzImage" "${BINARIES_DIR}/efi-part/bzImage"
|
||||
cp "${BINARIES_DIR}/rootfs.cpio.gz" "${BINARIES_DIR}/efi-part/initrd.cpio.gz"
|
||||
|
||||
cat > "${BINARIES_DIR}/efi-part/EFI/BOOT/grub.cfg" << GRUBCFG
|
||||
set default=0
|
||||
set timeout=0
|
||||
|
||||
menuentry "Cocos" {
|
||||
linux /bzImage console=ttyS0 roothash=${ROOT_HASH} systemd.verity=0 systemd.gpt_auto=0
|
||||
initrd /initrd.cpio.gz
|
||||
}
|
||||
GRUBCFG
|
||||
|
||||
# Regenerate bootx64.efi with --disable-shim-lock so GRUB can load the kernel
|
||||
# directly without requiring the shim bootloader (OVMF still verifies GRUB via
|
||||
# Secure Boot; shim is not needed when booting from a custom OVMF with own DB key).
|
||||
GRUB_CORE="$(ls -d "${BUILD_DIR}"/grub2-*/build-x86_64-efi/grub-core 2>/dev/null | head -1)"
|
||||
if [ -n "${GRUB_CORE}" ]; then
|
||||
echo "[post-image] Regenerating bootx64.efi with --disable-shim-lock..."
|
||||
"${HOST_DIR}/bin/grub-mkimage" \
|
||||
-d "${GRUB_CORE}" \
|
||||
-O x86_64-efi \
|
||||
-o "${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi" \
|
||||
-p "/EFI/BOOT" \
|
||||
--disable-shim-lock \
|
||||
boot linux echo normal part_gpt fat ls search || {
|
||||
echo "[post-image] FATAL: grub-mkimage failed"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
echo "[post-image] WARNING: GRUB core dir not found, skipping --disable-shim-lock rebuild"
|
||||
fi
|
||||
|
||||
# Sign GRUB and kernel for UEFI Secure Boot.
|
||||
# Keys are resolved in order: env var → board/secure-boot/ defaults.
|
||||
SB_KEY="${SB_KEY:-${COCOS_BOARD_DIR}/secure-boot/db.key}"
|
||||
SB_CERT="${SB_CERT:-${COCOS_BOARD_DIR}/secure-boot/db.crt}"
|
||||
if [ -f "${SB_KEY}" ] && [ -f "${SB_CERT}" ]; then
|
||||
echo "[post-image] Signing EFI binaries for Secure Boot..."
|
||||
sbsign --key "${SB_KEY}" --cert "${SB_CERT}" \
|
||||
--output "${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi" \
|
||||
"${BINARIES_DIR}/efi-part/EFI/BOOT/bootx64.efi" || {
|
||||
echo "[post-image] FATAL: Failed to sign bootx64.efi"
|
||||
exit 1
|
||||
}
|
||||
sbsign --key "${SB_KEY}" --cert "${SB_CERT}" \
|
||||
--output "${BINARIES_DIR}/efi-part/bzImage" \
|
||||
"${BINARIES_DIR}/efi-part/bzImage" || {
|
||||
echo "[post-image] FATAL: Failed to sign bzImage"
|
||||
exit 1
|
||||
}
|
||||
echo "[post-image] Secure Boot signing complete"
|
||||
else
|
||||
echo "[post-image] WARNING: Secure Boot keys not found — EFI binaries are unsigned"
|
||||
echo "[post-image] Default location: ${COCOS_BOARD_DIR}/secure-boot/db.key + db.crt"
|
||||
echo "[post-image] Override: SB_KEY=/path/to/db.key SB_CERT=/path/to/db.crt make"
|
||||
fi
|
||||
|
||||
GENIMAGE_CFG="${COCOS_BOARD_DIR}/genimage.cfg"
|
||||
if [ -f "${GENIMAGE_CFG}" ]; then
|
||||
GENIMAGE_TMP="${BUILD_DIR}/genimage.tmp"
|
||||
rm -rf "${GENIMAGE_TMP}"
|
||||
genimage \
|
||||
--rootpath "${TARGET_DIR}" \
|
||||
--tmppath "${GENIMAGE_TMP}" \
|
||||
--inputpath "${BINARIES_DIR}" \
|
||||
--outputpath "${BINARIES_DIR}" \
|
||||
--config "${GENIMAGE_CFG}"
|
||||
fi
|
||||
|
||||
if [[ "${DEFCONFIG_NAME}" =~ ^"cocos_*" ]]; then
|
||||
# Not a Qemu defconfig, can't test.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Search for "# qemu_*_defconfig" tag in all readme.txt files.
|
||||
# Qemu command line on multilines using back slash are accepted.
|
||||
# shellcheck disable=SC2086 # glob over each readme file
|
||||
QEMU_CMD_LINE="$(sed -r ':a; /\\$/N; s/\\\n//; s/\t/ /; ta; /# '"${DEFCONFIG_NAME}"'$/!d; s/#.*//' ${README_FILES})"
|
||||
|
||||
if [ -z "${QEMU_CMD_LINE}" ]; then
|
||||
# No Qemu cmd line found, can't test.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Remove output/images path since the script will be in
|
||||
# the same directory as the kernel and the rootfs images.
|
||||
QEMU_CMD_LINE="${QEMU_CMD_LINE//output\/images\//}"
|
||||
|
||||
# Remove -serial stdio if present, keep it as default args
|
||||
DEFAULT_ARGS="$(sed -r -e '/-serial stdio/!d; s/.*(-serial stdio).*/\1/' <<<"${QEMU_CMD_LINE}")"
|
||||
QEMU_CMD_LINE="${QEMU_CMD_LINE//-serial stdio/}"
|
||||
|
||||
# Remove any string before qemu-system-*
|
||||
QEMU_CMD_LINE="$(sed -r -e 's/^.*(qemu-system-)/\1/' <<<"${QEMU_CMD_LINE}")"
|
||||
|
||||
# Disable graphical output and redirect serial I/Os to console
|
||||
case ${DEFCONFIG_NAME} in
|
||||
(qemu_sh4eb_r2d_defconfig|qemu_sh4_r2d_defconfig)
|
||||
# Special case for SH4
|
||||
SERIAL_ARGS="-serial stdio -display none"
|
||||
;;
|
||||
(*)
|
||||
SERIAL_ARGS="-nographic"
|
||||
;;
|
||||
esac
|
||||
|
||||
sed -e "s|@SERIAL_ARGS@|${SERIAL_ARGS}|g" \
|
||||
-e "s|@DEFAULT_ARGS@|${DEFAULT_ARGS}|g" \
|
||||
-e "s|@QEMU_CMD_LINE@|${QEMU_CMD_LINE}|g" \
|
||||
-e "s|@HOST_DIR@|${HOST_DIR}|g" \
|
||||
<"${COCOS_BOARD_DIR}/start-qemu.sh.in" \
|
||||
>"${START_QEMU_SCRIPT}"
|
||||
chmod +x "${START_QEMU_SCRIPT}"
|
||||
@@ -0,0 +1,7 @@
|
||||
Run the emulation with:
|
||||
|
||||
qemu-system-x86_64 -M pc -kernel output/images/bzImage -drive file=output/images/rootfs.ext2,if=virtio,format=raw -append "rootwait root=/dev/vda console=tty1 console=ttyS0" -serial stdio -net nic,model=virtio -net user # cocos_defconfig
|
||||
|
||||
Optionally add -smp N to emulate a SMP system with N CPUs.
|
||||
|
||||
The login prompt will appear in the graphical window.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Private key must not be committed
|
||||
db.key
|
||||
db.crt
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
BINARIES_DIR="${0%/*}/"
|
||||
# shellcheck disable=SC2164
|
||||
cd "${BINARIES_DIR}"
|
||||
|
||||
mode_serial=false
|
||||
mode_sys_qemu=false
|
||||
while [ "$1" ]; do
|
||||
case "$1" in
|
||||
--serial-only|serial-only) mode_serial=true; shift;;
|
||||
--use-system-qemu) mode_sys_qemu=true; shift;;
|
||||
--) shift; break;;
|
||||
*) echo "unknown option: $1" >&2; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ${mode_serial}; then
|
||||
EXTRA_ARGS='@SERIAL_ARGS@'
|
||||
else
|
||||
EXTRA_ARGS='@DEFAULT_ARGS@'
|
||||
fi
|
||||
|
||||
if ! ${mode_sys_qemu}; then
|
||||
export PATH="@HOST_DIR@/bin:${PATH}"
|
||||
fi
|
||||
|
||||
exec @QEMU_CMD_LINE@ ${EXTRA_ARGS} "$@"
|
||||
@@ -0,0 +1,7 @@
|
||||
# Root is mounted read-only by the initramfs through dm-verity.
|
||||
# /cocos, /var, /tmp, and bind mounts are set up by the initramfs init script.
|
||||
/dev/mapper/root_verity / ext4 ro,defaults 0 0
|
||||
|
||||
# 9P virtio shares — provided by the hypervisor, optional (nofail)
|
||||
certs_share /etc/certs 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0
|
||||
env_share /etc/cocos 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"key-providers": {
|
||||
"attestation-agent": {
|
||||
"grpc": "127.0.0.1:50011"
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+265
@@ -0,0 +1,265 @@
|
||||
#!/bin/sh
|
||||
|
||||
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
if (exec 0</dev/console) 2>/dev/null; then
|
||||
exec 0</dev/console
|
||||
exec 1>/dev/console
|
||||
exec 2>/dev/console
|
||||
fi
|
||||
|
||||
echo "Welcome to the Cocos FDE test VM initramfs!"
|
||||
echo "This is a minimal initramfs environment used for testing the FDE provisioning flow."
|
||||
echo "If you see this message, the initramfs was loaded and executed successfully."
|
||||
echo "The initramfs will now attempt to provision the disk and mount the real root filesystem."
|
||||
echo "If any step fails, it will drop to a shell for debugging."
|
||||
|
||||
[ -d /dev ] || mkdir -m 0755 /dev
|
||||
[ -d /etc ] || mkdir -m 0755 /etc
|
||||
[ -d /root ] || mkdir -m 0700 /root
|
||||
[ -d /run ] || mkdir -m 0755 /run
|
||||
[ -d /sys ] || mkdir /sys
|
||||
[ -d /proc ] || mkdir /proc
|
||||
[ -d /tmp ] || mkdir /tmp
|
||||
|
||||
if [ -L /etc/resolv.conf ]; then
|
||||
RESOLV_TARGET="$(readlink /etc/resolv.conf)"
|
||||
case "$RESOLV_TARGET" in
|
||||
/*)
|
||||
RESOLV_PATH="$RESOLV_TARGET"
|
||||
;;
|
||||
*)
|
||||
RESOLV_PATH="/etc/$RESOLV_TARGET"
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$(dirname "$RESOLV_PATH")"
|
||||
[ -e "$RESOLV_PATH" ] || : > "$RESOLV_PATH"
|
||||
else
|
||||
[ -e /etc/resolv.conf ] || : > /etc/resolv.conf
|
||||
fi
|
||||
|
||||
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
|
||||
mount -t proc -o nodev,noexec,nosuid proc /proc
|
||||
|
||||
mkdir -p /sys/kernel/config
|
||||
if ! grep -q ' /sys/kernel/config ' /proc/mounts; then
|
||||
mount -t configfs configfs /sys/kernel/config 2>/dev/null || true
|
||||
fi
|
||||
|
||||
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
|
||||
mkdir /dev/pts
|
||||
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
|
||||
|
||||
MNT_DIR=/root
|
||||
BASE=$(pwd)
|
||||
|
||||
DST=/dev/sda
|
||||
ROOTFS_TYPE="ext4"
|
||||
ROOT_VERITY_MAP=root_verity
|
||||
ROOT_VERITY_MAPPER="/dev/mapper/$ROOT_VERITY_MAP"
|
||||
COCOS_MOUNT=/cocos
|
||||
COCOS_MAP=cocos_crypt
|
||||
COCOS_MAPPER="/dev/mapper/$COCOS_MAP"
|
||||
LUKS_PARAMS="--cipher aes-gcm-random --integrity aead"
|
||||
|
||||
settle_devices() {
|
||||
echo "[init] Waiting for devices to settle..."
|
||||
if command -v udevadm >/dev/null 2>&1; then
|
||||
udevadm settle --timeout=10 || sleep 2
|
||||
else
|
||||
sleep 2
|
||||
fi
|
||||
}
|
||||
|
||||
wipe_file() {
|
||||
file_path="$1"
|
||||
if [ -z "$file_path" ] || [ ! -e "$file_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
shred -vfz -n 3 "$file_path" 2>/dev/null || dd if=/dev/zero of="$file_path" bs=64 count=1
|
||||
rm -f "$file_path"
|
||||
}
|
||||
|
||||
partition_path() {
|
||||
disk="$1"
|
||||
partition="$2"
|
||||
|
||||
case "$disk" in
|
||||
*[0-9])
|
||||
printf '%sp%s\n' "$disk" "$partition"
|
||||
;;
|
||||
*)
|
||||
printf '%s%s\n' "$disk" "$partition"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
append_9p_entry() {
|
||||
pattern="$1"
|
||||
default_entry="$2"
|
||||
existing_entry=""
|
||||
|
||||
if [ -f "$FSTAB_BAK" ]; then
|
||||
existing_entry="$(grep -E "$pattern" "$FSTAB_BAK" | head -n 1 || true)"
|
||||
fi
|
||||
|
||||
if [ -n "$existing_entry" ]; then
|
||||
printf '%s\n' "$existing_entry" >> "$FSTAB"
|
||||
else
|
||||
printf '%s\n' "$default_entry" >> "$FSTAB"
|
||||
fi
|
||||
}
|
||||
|
||||
cmdline_arg() {
|
||||
key="$1"
|
||||
for arg in $(cat /proc/cmdline); do
|
||||
case "$arg" in
|
||||
"$key="*)
|
||||
printf '%s\n' "${arg#*=}"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "[init] Starting disk provisioning..."
|
||||
ROOT_PART="$(partition_path "$DST" 2)"
|
||||
VERITY_PART="$(partition_path "$DST" 3)"
|
||||
COCOS_PART="$(partition_path "$DST" 4)"
|
||||
ROOT_HASH="$(cmdline_arg roothash)"
|
||||
|
||||
if [ -z "$ROOT_HASH" ]; then
|
||||
echo "[init] FATAL: Missing roothash= on kernel command line"
|
||||
exec /bin/sh
|
||||
fi
|
||||
|
||||
settle_devices
|
||||
|
||||
for part in "$ROOT_PART" "$VERITY_PART" "$COCOS_PART"; do
|
||||
if [ ! -b "$part" ]; then
|
||||
echo "[init] FATAL: Could not find partition $part"
|
||||
echo "[init] Available block devices:"
|
||||
lsblk || ls -la /dev/ || true
|
||||
echo "[init] Dropping to shell."
|
||||
exec /bin/sh
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[init] Opening dm-verity root mapping..."
|
||||
veritysetup open "$ROOT_PART" "$ROOT_VERITY_MAP" "$VERITY_PART" "$ROOT_HASH" || {
|
||||
echo "[init] FATAL: Failed to open dm-verity mapping for root"
|
||||
exec /bin/sh
|
||||
}
|
||||
|
||||
echo "[init] Mounting root at $MNT_DIR (read-only)..."
|
||||
mount -o ro -t "$ROOTFS_TYPE" "$ROOT_VERITY_MAPPER" "$MNT_DIR" || {
|
||||
echo "[init] FATAL: Failed to mount verity root"
|
||||
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
|
||||
exec /bin/sh
|
||||
}
|
||||
|
||||
echo "[init] Generating ephemeral key for $COCOS_MOUNT..."
|
||||
dd if=/dev/urandom of=kk.bin bs=64 count=1 || {
|
||||
echo "[init] FATAL: Failed to generate encryption key"
|
||||
umount "$MNT_DIR" 2>/dev/null || true
|
||||
exec /bin/sh
|
||||
}
|
||||
KK_BIN=$BASE/kk.bin
|
||||
|
||||
cryptsetup luksFormat "$COCOS_PART" --type luks2 $LUKS_PARAMS --key-file="$KK_BIN" -q || {
|
||||
echo "[init] FATAL: LUKS format failed"
|
||||
umount "$MNT_DIR" 2>/dev/null || true
|
||||
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
|
||||
wipe_file "$KK_BIN"
|
||||
exec /bin/sh
|
||||
}
|
||||
|
||||
cryptsetup open "$COCOS_PART" "$COCOS_MAP" --key-file="$KK_BIN" || {
|
||||
echo "[init] FATAL: Failed to open LUKS container for $COCOS_MOUNT"
|
||||
umount "$MNT_DIR" 2>/dev/null || true
|
||||
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
|
||||
wipe_file "$KK_BIN"
|
||||
exec /bin/sh
|
||||
}
|
||||
|
||||
mkfs.ext4 -F -m 0 "$COCOS_MAPPER" >/dev/null || {
|
||||
echo "[init] FATAL: Failed to create ext4 filesystem for $COCOS_MOUNT"
|
||||
cryptsetup close "$COCOS_MAP" 2>/dev/null || true
|
||||
umount "$MNT_DIR" 2>/dev/null || true
|
||||
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
|
||||
wipe_file "$KK_BIN"
|
||||
exec /bin/sh
|
||||
}
|
||||
|
||||
echo "[init] Mounting encrypted $COCOS_MOUNT..."
|
||||
mkdir -p "$MNT_DIR$COCOS_MOUNT"
|
||||
mount -t ext4 "$COCOS_MAPPER" "$MNT_DIR$COCOS_MOUNT" || {
|
||||
echo "[init] FATAL: Failed to mount encrypted $COCOS_MOUNT filesystem"
|
||||
cryptsetup close "$COCOS_MAP" 2>/dev/null || true
|
||||
umount "$MNT_DIR" 2>/dev/null || true
|
||||
veritysetup close "$ROOT_VERITY_MAP" 2>/dev/null || true
|
||||
wipe_file "$KK_BIN"
|
||||
exec /bin/sh
|
||||
}
|
||||
|
||||
mkdir -p \
|
||||
"$MNT_DIR$COCOS_MOUNT/.cache/oci" \
|
||||
"$MNT_DIR$COCOS_MOUNT/datasets" \
|
||||
"$MNT_DIR$COCOS_MOUNT/docker" \
|
||||
"$MNT_DIR$COCOS_MOUNT/cocos_init"
|
||||
|
||||
# The root is read-only; provide tmpfs for writable system directories.
|
||||
mount -t tmpfs tmpfs "$MNT_DIR/tmp"
|
||||
mount -t tmpfs -o mode=0755 tmpfs "$MNT_DIR/var"
|
||||
|
||||
# Bind Docker's data root onto /cocos so large images don't exhaust RAM.
|
||||
mkdir -p "$MNT_DIR/var/lib/docker"
|
||||
mount --bind "$MNT_DIR$COCOS_MOUNT/docker" "$MNT_DIR/var/lib/docker"
|
||||
|
||||
# /cocos_init is on the read-only root; shadow it with a writable
|
||||
# copy on /cocos so agent setup scripts can write state alongside the scripts.
|
||||
if [ -d "$MNT_DIR/cocos_init" ]; then
|
||||
cp -a "$MNT_DIR/cocos_init/." "$MNT_DIR$COCOS_MOUNT/cocos_init/" 2>/dev/null || true
|
||||
mount --bind "$MNT_DIR$COCOS_MOUNT/cocos_init" "$MNT_DIR/cocos_init" || true
|
||||
fi
|
||||
|
||||
mount --move /proc $MNT_DIR/proc
|
||||
mount --move /sys $MNT_DIR/sys
|
||||
|
||||
FSTAB="$MNT_DIR/etc/fstab"
|
||||
FSTAB_BAK="$MNT_DIR/etc/fstab.bak"
|
||||
|
||||
mkdir -p "$MNT_DIR/etc/certs" "$MNT_DIR/etc/cocos" 2>/dev/null || true
|
||||
|
||||
if [ -f "$FSTAB" ]; then
|
||||
mv "$FSTAB" "$FSTAB_BAK"
|
||||
fi
|
||||
|
||||
cat > "$FSTAB" << EOF
|
||||
# Generated by init script
|
||||
$ROOT_VERITY_MAPPER / $ROOTFS_TYPE ro,defaults 0 0
|
||||
EOF
|
||||
|
||||
append_9p_entry \
|
||||
'^certs_share[[:space:]]+/etc/certs[[:space:]]+9p([[:space:]]|$)' \
|
||||
'certs_share /etc/certs 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0'
|
||||
|
||||
append_9p_entry \
|
||||
'^env_share[[:space:]]+/etc/cocos[[:space:]]+9p([[:space:]]|$)' \
|
||||
'env_share /etc/cocos 9p trans=virtio,version=9p2000.L,cache=mmap,nofail 0 0'
|
||||
|
||||
printf '%s\n' '# /cocos is mounted by the FDE initramfs using an ephemeral LUKS key.' >> "$FSTAB"
|
||||
|
||||
# Securely wipe the encryption key before switching root.
|
||||
echo "[init] Securely wiping the $COCOS_MOUNT encryption key..."
|
||||
wipe_file "$KK_BIN"
|
||||
|
||||
echo "[init] Switching to real root..."
|
||||
exec switch_root $MNT_DIR/ /sbin/init
|
||||
|
||||
# If switch_root somehow returns:
|
||||
echo "[init] switch_root failed, dropping to shell"
|
||||
exec /bin/sh
|
||||
@@ -0,0 +1,3 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos /etc/certs
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
d /var/log/cocos 0755 root root -
|
||||
d /run/cocos 0755 root root -
|
||||
@@ -0,0 +1,117 @@
|
||||
# Architecture
|
||||
BR2_x86_64=y
|
||||
|
||||
# System
|
||||
BR2_TARGET_GENERIC_HOSTNAME="cocos"
|
||||
BR2_TARGET_GENERIC_ISSUE="Welcome to Cocos"
|
||||
BR2_PACKAGE_DHCP=y
|
||||
BR2_PACKAGE_DHCP_CLIENT=y
|
||||
BR2_INIT_SYSTEMD=y
|
||||
BR2_SYSTEM_BIN_SH_BASH=y
|
||||
|
||||
# Filesystem
|
||||
# BR2_TARGET_ROOTFS_TAR is not set
|
||||
# Initramfs (rootfs.cpio.gz) is built by post-image.sh from only the FDE tools,
|
||||
# not from the full target rootfs. The full rootfs goes to rootfs.ext4 (disk image).
|
||||
BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_COCOS_PATH)/board/rootfs-overlay"
|
||||
|
||||
# Patches for existing Buildroot packages
|
||||
BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_COCOS_PATH)/patches"
|
||||
|
||||
# Bootloader
|
||||
BR2_TARGET_GRUB2=y
|
||||
BR2_TARGET_GRUB2_X86_64_EFI=y
|
||||
BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot linux echo normal part_gpt fat ls search"
|
||||
|
||||
# Disk image
|
||||
BR2_TARGET_ROOTFS_EXT2=y
|
||||
BR2_TARGET_ROOTFS_EXT2_4=y
|
||||
BR2_TARGET_ROOTFS_EXT2_SIZE="10G"
|
||||
BR2_PACKAGE_HOST_GENIMAGE=y
|
||||
BR2_PACKAGE_HOST_CRYPTSETUP=y
|
||||
|
||||
# Image
|
||||
BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-build.sh"
|
||||
|
||||
# Image
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="$(BR2_EXTERNAL_COCOS_PATH)/board/cocos/post-image.sh"
|
||||
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.11-rc7"
|
||||
|
||||
# Kernel
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_GIT=y
|
||||
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"
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||
|
||||
# host-qemu for gitlab testing
|
||||
BR2_PACKAGE_HOST_QEMU=y
|
||||
BR2_PACKAGE_HOST_QEMU_SYSTEM_MODE=y
|
||||
|
||||
# Python
|
||||
BR2_PACKAGE_PYTHON3=y
|
||||
BR2_PACKAGE_PYTHON_PIP=y
|
||||
BR2_PACKAGE_BZIP2=y
|
||||
BR2_PACKAGE_XZ=y
|
||||
BR2_PACKAGE_ZIP=y
|
||||
BR2_PACKAGE_PYTHON3_ZLIB=y
|
||||
BR2_PACKAGE_PYTHON3_XZ=y
|
||||
BR2_PACKAGE_PYTHON3_BZIP2=y
|
||||
BR2_INSTALL_LIBSTDCPP=y
|
||||
BR2_TOOLCHAIN_BUILDROOT_CXX=y
|
||||
BR2_PACKAGE_HOST_GCC_TARGET=y
|
||||
BR2_TOOLCHAIN_BUILDROOT_LIBSTDCPP=y
|
||||
BR2_PACKAGE_GCC=y
|
||||
BR2_PACKAGE_GCC_TARGET=y
|
||||
BR2_PACKAGE_LIBSTDCPP=y
|
||||
|
||||
# FDE
|
||||
BR2_PACKAGE_NBD=y
|
||||
BR2_PACKAGE_NBD_CLIENT=y
|
||||
BR2_PACKAGE_CRYPTSETUP=y
|
||||
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
|
||||
BR2_PACKAGE_EUDEV=y
|
||||
BR2_PACKAGE_HAS_UDEV=y
|
||||
BR2_PACKAGE_MULTIPATH_TOOLS=y
|
||||
BR2_PACKAGE_UTIL_LINUX_BINARIES=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_PAHOLE=y
|
||||
|
||||
# TPM2
|
||||
BR2_PACKAGE_TPM2_TOOLS=y
|
||||
BR2_PACKAGE_COREUTILS=y
|
||||
|
||||
# Docker
|
||||
BR2_PACKAGE_LIBSECCOMP_ARCH_SUPPORTS=y
|
||||
BR2_PACKAGE_LIBSECCOMP=y
|
||||
BR2_PACKAGE_CA_CERTIFICATES=y
|
||||
BR2_PACKAGE_DOCKER_CLI=y
|
||||
BR2_PACKAGE_DOCKER_COMPOSE=y
|
||||
BR2_PACKAGE_DOCKER_ENGINE=y
|
||||
BR2_PACKAGE_CONTAINERD=y
|
||||
BR2_PACKAGE_RUNC=y
|
||||
BR2_PACKAGE_IPTABLES=y
|
||||
|
||||
# Skopeo for OCI image handling with CoCo Keyprovider
|
||||
BR2_PACKAGE_SKOPEO=y
|
||||
BR2_PACKAGE_GPGME=y
|
||||
BR2_PACKAGE_LVM2=y
|
||||
BR2_PACKAGE_LVM2_STANDARD_INSTALL=y
|
||||
BR2_PACKAGE_9PFS=y
|
||||
|
||||
# Host tools
|
||||
BR2_PACKAGE_HOST_RUSTC=y
|
||||
BR2_PACKAGE_HOST_RUST_BIN=y
|
||||
|
||||
# Cocos AI Packages
|
||||
BR2_PACKAGE_AGENT=y
|
||||
# BR2_PACKAGE_CC_ATTESTATION_AGENT is not set
|
||||
@@ -0,0 +1,2 @@
|
||||
name: COCOS
|
||||
desc: External buildroot tree for Cocos AI
|
||||
@@ -0,0 +1 @@
|
||||
include $(sort $(wildcard $(BR2_EXTERNAL_COCOS_PATH)/package/*/*.mk))
|
||||
@@ -0,0 +1,13 @@
|
||||
config BR2_PACKAGE_AGENT
|
||||
bool "agent"
|
||||
default y
|
||||
select BR2_PACKAGE_ATTESTATION_SERVICE
|
||||
select BR2_PACKAGE_LOG_FORWARDER
|
||||
select BR2_PACKAGE_COMPUTATION_RUNNER
|
||||
select BR2_PACKAGE_INGRESS_PROXY
|
||||
select BR2_PACKAGE_EGRESS_PROXY
|
||||
help
|
||||
Confidential Computing Agent is a state machine capable of
|
||||
receiving datasets and algorithm, running computations, and
|
||||
fetching the attestation report from within the
|
||||
Confidential VM.
|
||||
@@ -0,0 +1,27 @@
|
||||
################################################################################
|
||||
#
|
||||
# Cocos AI Agent
|
||||
#
|
||||
################################################################################
|
||||
|
||||
AGENT_VERSION = main
|
||||
AGENT_SITE = $(call github,ultravioletrs,cocos,$(AGENT_VERSION))
|
||||
|
||||
define AGENT_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) agent EMBED_ENABLED=$(AGENT_EMBED_ENABLED)
|
||||
endef
|
||||
|
||||
define AGENT_INSTALL_TARGET_CMDS
|
||||
mkdir -p $(TARGET_DIR)/cocos/
|
||||
mkdir -p $(TARGET_DIR)/var/log/cocos
|
||||
mkdir -p $(TARGET_DIR)/cocos_init/
|
||||
$(INSTALL) -D -m 0750 $(@D)/build/cocos-agent $(TARGET_DIR)/bin
|
||||
endef
|
||||
|
||||
define AGENT_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0640 $(@D)/init/systemd/cocos-agent.service $(TARGET_DIR)/usr/lib/systemd/system/cocos-agent.service
|
||||
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_setup.sh $(TARGET_DIR)/cocos_init/agent_setup.sh
|
||||
$(INSTALL) -D -m 0750 $(@D)/init/systemd/agent_start_script.sh $(TARGET_DIR)/cocos_init/agent_start_script.sh
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,11 @@
|
||||
config BR2_PACKAGE_ATTESTATION_SERVICE
|
||||
bool
|
||||
default y
|
||||
help
|
||||
Cocos AI attestation service that generates EAT tokens
|
||||
for TEE attestation (SNP, TDX, vTPM, Azure).
|
||||
|
||||
This service can optionally use the Confidential Containers
|
||||
attestation-agent as a backend provider via gRPC.
|
||||
|
||||
https://github.com/ultravioletrs/cocos
|
||||
@@ -0,0 +1,34 @@
|
||||
################################################################################
|
||||
#
|
||||
# attestation-service
|
||||
#
|
||||
################################################################################
|
||||
|
||||
ATTESTATION_SERVICE_VERSION = main
|
||||
ATTESTATION_SERVICE_SITE = $(call github,ultravioletrs,cocos,$(ATTESTATION_SERVICE_VERSION))
|
||||
|
||||
define ATTESTATION_SERVICE_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) attestation-service
|
||||
endef
|
||||
|
||||
define ATTESTATION_SERVICE_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0755 $(@D)/build/cocos-attestation-service $(TARGET_DIR)/usr/bin/attestation-service
|
||||
endef
|
||||
|
||||
ifeq ($(BR2_PACKAGE_CC_ATTESTATION_AGENT),y)
|
||||
define ATTESTATION_SERVICE_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0640 $(@D)/init/systemd/attestation-service.service $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service
|
||||
$(INSTALL) -D -m 0750 $(@D)/init/systemd/attestation_setup.sh $(TARGET_DIR)/cocos_init/attestation_setup.sh
|
||||
# CC attestation agent is already enabled by default
|
||||
endef
|
||||
else
|
||||
define ATTESTATION_SERVICE_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0640 $(@D)/init/systemd/attestation-service.service $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service
|
||||
$(INSTALL) -D -m 0750 $(@D)/init/systemd/attestation_setup.sh $(TARGET_DIR)/cocos_init/attestation_setup.sh
|
||||
# Disable CC attestation agent backend if not selected
|
||||
sed -i 's/USE_CC_ATTESTATION_AGENT=true/USE_CC_ATTESTATION_AGENT=false/' $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service
|
||||
sed -i '/Wants=attestation-agent.service/d' $(TARGET_DIR)/usr/lib/systemd/system/attestation-service.service
|
||||
endef
|
||||
endif
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,28 @@
|
||||
config BR2_PACKAGE_CC_ATTESTATION_AGENT
|
||||
bool "cc-attestation-agent"
|
||||
select BR2_PACKAGE_PROTOBUF
|
||||
select BR2_PACKAGE_OPENSSL
|
||||
select BR2_PACKAGE_TPM2_TSS
|
||||
help
|
||||
Confidential Containers attestation-agent for TEE attestation.
|
||||
|
||||
Optional backend for the Cocos AI attestation service that
|
||||
provides KBS protocol support for remote attestation and
|
||||
encrypted secret provisioning.
|
||||
|
||||
https://github.com/confidential-containers/guest-components
|
||||
|
||||
if BR2_PACKAGE_CC_ATTESTATION_AGENT
|
||||
|
||||
config BR2_PACKAGE_CC_ATTESTATION_AGENT_KBS_URL
|
||||
string "Default KBS URL (optional)"
|
||||
default ""
|
||||
help
|
||||
Optional default KBS (Key Broker Service) URL for remote
|
||||
attestation and secret provisioning.
|
||||
|
||||
Leave empty to operate in local attestation mode only.
|
||||
|
||||
Example: https://kbs.example.com:8080
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Setup permissions for attestation socket directory
|
||||
|
||||
mkdir -p /run/cocos
|
||||
chmod 755 /run/cocos
|
||||
@@ -0,0 +1,37 @@
|
||||
################################################################################
|
||||
#
|
||||
# cc-attestation-agent
|
||||
#
|
||||
################################################################################
|
||||
|
||||
CC_ATTESTATION_AGENT_VERSION = mvp-runner
|
||||
CC_ATTESTATION_AGENT_SITE = $(call github,rodneyosodo,guest-components,$(CC_ATTESTATION_AGENT_VERSION))
|
||||
CC_ATTESTATION_AGENT_LICENSE = Apache-2.0
|
||||
CC_ATTESTATION_AGENT_LICENSE_FILES = LICENSE
|
||||
|
||||
CC_ATTESTATION_AGENT_DEPENDENCIES = host-rustc openssl protobuf tpm2-tss
|
||||
|
||||
# Build the attestation-agent from the guest-components repository with gRPC support
|
||||
define CC_ATTESTATION_AGENT_BUILD_CMDS
|
||||
cd $(@D)/attestation-agent && \
|
||||
$(TARGET_MAKE_ENV) \
|
||||
CARGO_HOME=$(@D)/.cargo \
|
||||
make ATTESTER=all-attesters ttrpc=false
|
||||
endef
|
||||
|
||||
define CC_ATTESTATION_AGENT_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0755 \
|
||||
$(@D)/target/$(RUSTC_TARGET_NAME)/release/attestation-agent \
|
||||
$(TARGET_DIR)/usr/bin/attestation-agent
|
||||
endef
|
||||
|
||||
define CC_ATTESTATION_AGENT_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0644 \
|
||||
$(BR2_EXTERNAL_COCOS_PATH)/package/cc-attestation-agent/cc-attestation-agent.service \
|
||||
$(TARGET_DIR)/usr/lib/systemd/system/attestation-agent.service
|
||||
$(INSTALL) -D -m 0750 \
|
||||
$(BR2_EXTERNAL_COCOS_PATH)/package/cc-attestation-agent/cc-attestation-agent-setup.sh \
|
||||
$(TARGET_DIR)/cocos_init/attestation_setup.sh
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Confidential Containers Attestation Agent (gRPC)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/attestation-agent --attestation_sock 127.0.0.1:50002
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
Environment=RUST_LOG=info
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,11 @@
|
||||
config BR2_PACKAGE_COCO_KEYPROVIDER
|
||||
bool "coco-keyprovider"
|
||||
depends on BR2_PACKAGE_HOST_RUSTC_ARCH_SUPPORTS
|
||||
select BR2_PACKAGE_HOST_RUSTC
|
||||
help
|
||||
CoCo Keyprovider is a keyprovider tool for generating and
|
||||
decrypting CoCo-compatible encrypted images. It implements
|
||||
the ocicrypt keyprovider protocol to decrypt OCI image layers
|
||||
using the Key Broker Service (KBS).
|
||||
|
||||
https://github.com/confidential-containers/guest-components
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Read kernel command line
|
||||
CMDLINE=$(cat /proc/cmdline)
|
||||
|
||||
# Extract agent.aa_kbc_params value
|
||||
# Format: agent.aa_kbc_params=cc_kbc::URL
|
||||
PARAMS=$(echo "$CMDLINE" | tr ' ' '\n' | grep '^agent.aa_kbc_params=' | cut -d= -f2-)
|
||||
|
||||
if [ -n "$PARAMS" ]; then
|
||||
# Extract URL part (after ::)
|
||||
KBS_URL="${PARAMS#*::}"
|
||||
if [ -n "$KBS_URL" ]; then
|
||||
echo "[coco-keyprovider-setup] Detected KBS URL from kernel cmdline: $KBS_URL"
|
||||
KBS_ARG="--kbs $KBS_URL"
|
||||
fi
|
||||
else
|
||||
echo "[coco-keyprovider-setup] No agent.aa_kbc_params found in kernel cmdline. Starting without --kbs."
|
||||
fi
|
||||
|
||||
# COCO_KP_SOCKET is set by EnvironmentFile in .service
|
||||
if [ -z "$COCO_KP_SOCKET" ]; then
|
||||
COCO_KP_SOCKET="127.0.0.1:50011"
|
||||
fi
|
||||
|
||||
echo "[coco-keyprovider-setup] Starting coco_keyprovider listening on $COCO_KP_SOCKET $KBS_ARG"
|
||||
exec /usr/local/bin/coco_keyprovider --socket "$COCO_KP_SOCKET" $KBS_ARG
|
||||
@@ -0,0 +1,3 @@
|
||||
# CoCo Keyprovider Environment Variables
|
||||
COCO_KP_SOCKET=127.0.0.1:50011
|
||||
RUST_LOG=info
|
||||
@@ -0,0 +1,34 @@
|
||||
################################################################################
|
||||
#
|
||||
# coco-keyprovider
|
||||
#
|
||||
################################################################################
|
||||
|
||||
COCO_KEYPROVIDER_VERSION = mvp-runner
|
||||
COCO_KEYPROVIDER_SITE = $(call github,rodneyosodo,guest-components,$(COCO_KEYPROVIDER_VERSION))
|
||||
COCO_KEYPROVIDER_LICENSE = Apache-2.0
|
||||
COCO_KEYPROVIDER_LICENSE_FILES = LICENSE
|
||||
|
||||
COCO_KEYPROVIDER_DEPENDENCIES = host-rustc
|
||||
|
||||
define COCO_KEYPROVIDER_BUILD_CMDS
|
||||
cd $(@D)/attestation-agent/coco_keyprovider && \
|
||||
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) \
|
||||
CARGO_HOME=$(HOST_DIR)/share/cargo \
|
||||
cargo build --release --target=$(RUSTC_TARGET_NAME)
|
||||
endef
|
||||
|
||||
define COCO_KEYPROVIDER_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0755 $(@D)/target/$(RUSTC_TARGET_NAME)/release/coco_keyprovider \
|
||||
$(TARGET_DIR)/usr/local/bin/coco_keyprovider
|
||||
$(INSTALL) -D -m 0755 $(BR2_EXTERNAL_COCOS_PATH)/package/coco-keyprovider/coco-keyprovider-setup.sh \
|
||||
$(TARGET_DIR)/usr/local/bin/coco-keyprovider-setup.sh
|
||||
$(INSTALL) -D -m 0644 $(BR2_EXTERNAL_COCOS_PATH)/package/coco-keyprovider/coco-keyprovider.service \
|
||||
$(TARGET_DIR)/etc/systemd/system/coco-keyprovider.service
|
||||
$(INSTALL) -D -m 0644 $(BR2_EXTERNAL_COCOS_PATH)/package/coco-keyprovider/coco-keyprovider.default \
|
||||
$(TARGET_DIR)/etc/default/coco-keyprovider
|
||||
mkdir -p $(TARGET_DIR)/etc
|
||||
echo '{"key-providers": {"attestation-agent": {"grpc": "127.0.0.1:50011"}}}' > $(TARGET_DIR)/etc/ocicrypt_keyprovider.conf
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=CoCo Keyprovider for Confidential Containers
|
||||
Documentation=https://github.com/confidential-containers/guest-components
|
||||
After=network-online.target attestation-agent.service
|
||||
Wants=network-online.target
|
||||
Requires=attestation-agent.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/default/coco-keyprovider
|
||||
RuntimeDirectory=coco-keyprovider
|
||||
ExecStart=/usr/local/bin/coco-keyprovider-setup.sh
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,5 @@
|
||||
config BR2_PACKAGE_COMPUTATION_RUNNER
|
||||
bool "computation-runner"
|
||||
select BR2_PACKAGE_LOG_FORWARDER
|
||||
help
|
||||
Cocos AI Computation Runner service.
|
||||
@@ -0,0 +1,22 @@
|
||||
################################################################################
|
||||
#
|
||||
# computation-runner
|
||||
#
|
||||
################################################################################
|
||||
|
||||
COMPUTATION_RUNNER_VERSION = main
|
||||
COMPUTATION_RUNNER_SITE = $(call github,ultravioletrs,cocos,$(COMPUTATION_RUNNER_VERSION))
|
||||
|
||||
define COMPUTATION_RUNNER_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) computation-runner
|
||||
endef
|
||||
|
||||
define COMPUTATION_RUNNER_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0750 $(@D)/build/cocos-computation-runner $(TARGET_DIR)/usr/bin/computation-runner
|
||||
endef
|
||||
|
||||
define COMPUTATION_RUNNER_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0640 $(@D)/init/systemd/computation-runner.service $(TARGET_DIR)/usr/lib/systemd/system/computation-runner.service
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,6 @@
|
||||
config BR2_PACKAGE_EGRESS_PROXY
|
||||
bool "egress-proxy"
|
||||
help
|
||||
Cocos AI Egress Proxy Service.
|
||||
|
||||
https://github.com/ultravioletrs/cocos
|
||||
@@ -0,0 +1,22 @@
|
||||
################################################################################
|
||||
#
|
||||
# Cocos AI Egress Proxy
|
||||
#
|
||||
################################################################################
|
||||
|
||||
EGRESS_PROXY_VERSION = main
|
||||
EGRESS_PROXY_SITE = $(call github,ultravioletrs,cocos,$(EGRESS_PROXY_VERSION))
|
||||
|
||||
define EGRESS_PROXY_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) egress-proxy
|
||||
endef
|
||||
|
||||
define EGRESS_PROXY_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0755 $(@D)/build/cocos-egress-proxy $(TARGET_DIR)/usr/bin/egress-proxy
|
||||
endef
|
||||
|
||||
define EGRESS_PROXY_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0644 $(@D)/init/systemd/egress-proxy.service $(TARGET_DIR)/usr/lib/systemd/system/egress-proxy.service
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,4 @@
|
||||
config BR2_PACKAGE_INGRESS_PROXY
|
||||
bool "ingress-proxy"
|
||||
help
|
||||
Cocos Ingress Proxy service.
|
||||
@@ -0,0 +1,22 @@
|
||||
################################################################################
|
||||
#
|
||||
# ingress-proxy
|
||||
#
|
||||
################################################################################
|
||||
|
||||
INGRESS_PROXY_VERSION = main
|
||||
INGRESS_PROXY_SITE = $(call github,ultravioletrs,cocos,$(INGRESS_PROXY_VERSION))
|
||||
|
||||
define INGRESS_PROXY_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) ingress-proxy
|
||||
endef
|
||||
|
||||
define INGRESS_PROXY_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0750 $(@D)/build/cocos-ingress-proxy $(TARGET_DIR)/usr/bin/ingress-proxy
|
||||
endef
|
||||
|
||||
# NOTE: The ingress-proxy is managed per-computation by the agent, not as a standalone
|
||||
# systemd service. The binary is installed for use by the agent, but no systemd service
|
||||
# is created.
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,4 @@
|
||||
config BR2_PACKAGE_LOG_FORWARDER
|
||||
bool "log-forwarder"
|
||||
help
|
||||
Cocos AI Log Forwarder service.
|
||||
@@ -0,0 +1,22 @@
|
||||
################################################################################
|
||||
#
|
||||
# log-forwarder
|
||||
#
|
||||
################################################################################
|
||||
|
||||
LOG_FORWARDER_VERSION = main
|
||||
LOG_FORWARDER_SITE = $(call github,ultravioletrs,cocos,$(LOG_FORWARDER_VERSION))
|
||||
|
||||
define LOG_FORWARDER_BUILD_CMDS
|
||||
$(MAKE) -C $(@D) log-forwarder
|
||||
endef
|
||||
|
||||
define LOG_FORWARDER_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0750 $(@D)/build/cocos-log-forwarder $(TARGET_DIR)/usr/bin/log-forwarder
|
||||
endef
|
||||
|
||||
define LOG_FORWARDER_INSTALL_INIT_SYSTEMD
|
||||
$(INSTALL) -D -m 0640 $(@D)/init/systemd/log-forwarder.service $(TARGET_DIR)/usr/lib/systemd/system/log-forwarder.service
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,6 @@
|
||||
config BR2_PACKAGE_WASMEDGE
|
||||
bool "wasmedge"
|
||||
default y
|
||||
help
|
||||
Wasmedge is a standalone runtime for WebAssembly.
|
||||
https://wasmedge.org/docs/
|
||||
@@ -0,0 +1,8 @@
|
||||
WASMEDGE_DOWNLOAD_URL = https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh
|
||||
|
||||
define WASMEDGE_INSTALL_TARGET_CMDS
|
||||
curl -sSf $(WASMEDGE_DOWNLOAD_URL) | bash -s -- -p $(TARGET_DIR)/usr -v 0.14.1
|
||||
echo "source /usr/env" >> $(TARGET_DIR)/etc/profile
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1,34 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
Subject: [PATCH] efi: skip lockdown when built with --disable-shim-lock
|
||||
|
||||
When GRUB is built with --disable-shim-lock, grub_shim_lock_verifier_setup()
|
||||
returns early without registering the shim_lock verifier. However
|
||||
grub_lockdown() was called unconditionally before that, registering the
|
||||
lockdown_verifier which marks kernel files as DEFER_AUTH. With no verifier
|
||||
present to approve them, every kernel load fails with "verification requested
|
||||
but nobody cares".
|
||||
|
||||
Fix by calling grub_shim_lock_verifier_setup() first and only calling
|
||||
grub_lockdown() if shim_lock is actually active. This preserves full
|
||||
lockdown behaviour in shim-based chains while allowing direct
|
||||
OVMF->GRUB->kernel boot with a custom DB key and no shim.
|
||||
|
||||
Signed-off-by: Cocos AI <build@cocos.ai>
|
||||
---
|
||||
grub-core/kern/efi/init.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/grub-core/kern/efi/init.c b/grub-core/kern/efi/init.c
|
||||
--- a/grub-core/kern/efi/init.c
|
||||
+++ b/grub-core/kern/efi/init.c
|
||||
@@ -122,8 +122,9 @@
|
||||
*/
|
||||
if (grub_efi_get_secureboot () == GRUB_EFI_SECUREBOOT_MODE_ENABLED)
|
||||
{
|
||||
- grub_lockdown ();
|
||||
grub_shim_lock_verifier_setup ();
|
||||
+ if (grub_is_shim_lock_enabled ())
|
||||
+ grub_lockdown ();
|
||||
}
|
||||
|
||||
grub_efi_system_table->boot_services->set_watchdog_timer (0, 0, 0, NULL);
|
||||
+1
-1
@@ -10,7 +10,7 @@ HAL uses [Buildroot](https://buildroot.org/)'s [_External Tree_ mechanism](https
|
||||
git clone git@github.com:ultravioletrs/cocos.git
|
||||
git clone git@github.com:buildroot/buildroot.git
|
||||
cd buildroot
|
||||
git checkout 2025.08-rc3
|
||||
git checkout 2025.11
|
||||
make BR2_EXTERNAL=../cocos/hal/linux cocos_defconfig
|
||||
# Execute 'make menuconfig' only if you want to make additional configuration changes to Buildroot.
|
||||
make menuconfig
|
||||
|
||||
@@ -22,5 +22,8 @@ if [ ! -d "$WORK_DIR" ]; then
|
||||
mkdir -p $WORK_DIR
|
||||
fi
|
||||
|
||||
# Resize the root file system to 100%
|
||||
mount -o remount,size=100% /
|
||||
# RAM-only agent images use tmpfs as the root filesystem
|
||||
ROOT_FSTYPE=$(awk '$2 == "/" { print $3; exit }' /proc/mounts)
|
||||
if [ "$ROOT_FSTYPE" = "tmpfs" ]; then
|
||||
mount -o remount,size=100% /
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Unit]
|
||||
Description=Cocos AI agent
|
||||
After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service coco-keyprovider.service
|
||||
Requires=log-forwarder.service computation-runner.service egress-proxy.service coco-keyprovider.service
|
||||
After=network.target attestation-service.service log-forwarder.service computation-runner.service egress-proxy.service
|
||||
Requires=log-forwarder.service computation-runner.service egress-proxy.service
|
||||
Before=docker.service
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ package attestation
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
|
||||
+36
-4
@@ -49,6 +49,12 @@ The service is configured using the environment variables from the following tab
|
||||
| MANAGER_QEMU_VIRTIO_NET_PCI_ROMFILE | The file path for the ROM image for the virtio-net PCI device. | |
|
||||
| MANAGER_QEMU_DISK_IMG_KERNEL_FILE | The file path for the kernel image. | img/bzImage |
|
||||
| MANAGER_QEMU_DISK_IMG_ROOTFS_FILE | The file path for the root filesystem image. | img/rootfs.cpio.gz |
|
||||
| MANAGER_QEMU_ENABLE_DISK | Whether to attach a writable qcow2 disk to the CVM. | false |
|
||||
| MANAGER_QEMU_SRC_DISK_FILE | Path to a qcow2 image whose virtual size is used to size the per-VM writable disk. | img/enc_os.qcow2 |
|
||||
| MANAGER_QEMU_DST_DISK_FILE | Runtime path of the per-VM writable disk created by the manager. | |
|
||||
| MANAGER_QEMU_DISK_ID | The QEMU drive identifier for the attached disk. | disk0 |
|
||||
| MANAGER_QEMU_DISK_FORMAT | The format of the attached disk image. | qcow2 |
|
||||
| MANAGER_QEMU_DISK_SCSI_ID | The SCSI controller identifier used for the attached disk. | scsi0 |
|
||||
| MANAGER_QEMU_SEV_SNP_ID | The ID for the Secure Encrypted Virtualization (SEV-SNP) device. | sev0 |
|
||||
| MANAGER_QEMU_SEV_SNP_CBITPOS | The position of the C-bit in the physical address. | 51 |
|
||||
| MANAGER_QEMU_SEV_SNP_REDUCED_PHYS_BITS | The number of reduced physical address bits for SEV-SNP. | 1 |
|
||||
@@ -112,6 +118,20 @@ Once the image is built copy the kernel and rootfs image to `cmd/manager/img` fr
|
||||
|
||||
Another option is to use release versions of EOS that can be downloaded from the [Cocos GitHub repository](https://github.com/ultravioletrs/cocos/releases).
|
||||
|
||||
#### Optional writable disk
|
||||
|
||||
If you want the manager to attach a writable disk to each CVM, place a qcow2 reference image at `cmd/manager/img/enc_os.qcow2`, or point `MANAGER_QEMU_SRC_DISK_FILE` to another qcow2 file.
|
||||
|
||||
When `MANAGER_QEMU_ENABLE_DISK=true`, the manager:
|
||||
|
||||
- reads the virtual size of `MANAGER_QEMU_SRC_DISK_FILE` with `qemu-img info`
|
||||
- creates a per-VM qcow2 disk under `/tmp/cvmDisk-<uuid>.qcow2`
|
||||
- sizes the disk to the source image size plus 1 GiB, leaving room for the LUKS header
|
||||
- attaches the disk through a virtio-scsi controller
|
||||
- removes the temporary disk again when the VM stops
|
||||
|
||||
`MANAGER_QEMU_DST_DISK_FILE` is primarily a runtime value. In the normal manager flow it is populated automatically and usually does not need to be set manually.
|
||||
|
||||
#### Test VM creation
|
||||
|
||||
```sh
|
||||
@@ -207,7 +227,7 @@ nc -zv localhost 7020
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Now you are able to use `Manager` with `Agent`. Namely, `Manager` will create a VM with a separate OVMF variables file on manager `/run` request.
|
||||
Now you are able to use `Manager` with `Agent`. On each manager `/run` request, the manager creates a VM with a separate OVMF variables file and, when enabled, a per-VM writable qcow2 disk.
|
||||
|
||||
### OVMF
|
||||
|
||||
@@ -284,6 +304,18 @@ MANAGER_QEMU_OVMF_FILE=<path to OVMF file> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
To enable writable disk support, start manager like this
|
||||
|
||||
```sh
|
||||
MANAGER_GRPC_URL=localhost:7001 \
|
||||
MANAGER_LOG_LEVEL=debug \
|
||||
MANAGER_QEMU_ENABLE_DISK=true \
|
||||
MANAGER_QEMU_SRC_DISK_FILE=<path to reference qcow2 image> \
|
||||
./build/cocos-manager
|
||||
```
|
||||
|
||||
The reference qcow2 image is used to determine the disk size. The manager creates a fresh writable qcow2 disk for each VM under `/tmp` and deletes it on shutdown.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If the `ps aux | grep qemu-system-x86_64` give you something like this
|
||||
@@ -294,16 +326,16 @@ darko 13913 0.0 0.0 0 0 pts/2 Z+ 20:17 0:00 [qemu-system-
|
||||
|
||||
means that the a QEMU virtual machine that is currently defunct, meaning that it is no longer running. More precisely, the defunct process in the output is also known as a ["zombie" process](https://en.wikipedia.org/wiki/Zombie_process).
|
||||
|
||||
You can troubleshoot the VM launch procedure by running directly `qemu-system-x86_64` command. When you run `manager` with `MANAGER_LOG_LEVEL=info` env var set, it prints out the entire command used to launch a VM. The relevant part of the log might look like this
|
||||
You can troubleshoot the VM launch procedure by running directly `qemu-system-x86_64` command. When you run `manager` with `MANAGER_LOG_LEVEL=info` env var set, it prints out the entire command used to launch a VM. When writable disk support is enabled, the relevant part of the log might look like this
|
||||
|
||||
```
|
||||
{"level":"info","message":"/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","ts":"2023-08-14T18:29:19.2653908Z"}
|
||||
{"level":"info","message":"/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=/tmp/OVMF_VARS-<uuid>.fd -netdev user,id=vmnic-<uuid>,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic-<uuid>,addr=0x2,romfile= -drive file=/tmp/cvmDisk-<uuid>.qcow2,if=none,id=disk0,format=qcow2 -device virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=true -device scsi-hd,drive=disk0,bus=scsi0.0 -kernel img/bzImage -append quiet console=null -initrd img/rootfs.cpio.gz -nographic -monitor pty","ts":"2026-04-27T00:00:00Z"}
|
||||
```
|
||||
|
||||
You can run the command - the value of the `"message"` key - directly in the terminal:
|
||||
|
||||
```sh
|
||||
/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
|
||||
/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=/tmp/OVMF_VARS-<uuid>.fd -netdev user,id=vmnic-<uuid>,hostfwd=tcp::7020-:7002 -device virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic-<uuid>,addr=0x2,romfile= -drive file=/tmp/cvmDisk-<uuid>.qcow2,if=none,id=disk0,format=qcow2 -device virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=true -device scsi-hd,drive=disk0,bus=scsi0.0 -kernel img/bzImage -append "quiet console=null" -initrd img/rootfs.cpio.gz -nographic -monitor pty
|
||||
```
|
||||
|
||||
and look for the possible problems. This problems can usually be solved by using the adequate env var assignments. Look in the `manager/qemu/config.go` file to see the recognized env vars. Don't forget to prepend `MANAGER_QEMU_` to the name of the env vars.
|
||||
|
||||
@@ -6,14 +6,14 @@ package http
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/supermq"
|
||||
"github.com/absmach/magistrala"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// MakeHandler returns a HTTP handler for API endpoints.
|
||||
func MakeHandler(r *chi.Mux, svcName, instanceID string) http.Handler {
|
||||
r.Get("/health", supermq.Health(svcName, instanceID))
|
||||
r.Get("/health", magistrala.Health(svcName, instanceID))
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return r
|
||||
|
||||
+92
-6
@@ -4,6 +4,7 @@ package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
@@ -48,7 +49,7 @@ type VirtioNetPciConfig struct {
|
||||
ROMFile string `env:"VIRTIO_NET_PCI_ROMFILE"`
|
||||
}
|
||||
|
||||
type DiskImgConfig struct {
|
||||
type KernelConfig struct {
|
||||
KernelFile string `env:"DISK_IMG_KERNEL_FILE" envDefault:"img/bzImage"`
|
||||
RootFsFile string `env:"DISK_IMG_ROOTFS_FILE" envDefault:"img/rootfs.cpio.gz"`
|
||||
}
|
||||
@@ -72,9 +73,26 @@ type IGVMConfig struct {
|
||||
File string `env:"IGVM_FILE" envDefault:"/root/coconut-qemu.igvm"`
|
||||
}
|
||||
|
||||
type DiskConfig struct {
|
||||
SrcFile string `env:"SRC_DISK_FILE" envDefault:"img/enc_os.qcow2"`
|
||||
DstFile string `env:"DST_DISK_FILE" envDefault:""`
|
||||
ID string `env:"DISK_ID" envDefault:"disk0"`
|
||||
Format string `env:"DISK_FORMAT" envDefault:"qcow2"`
|
||||
SCSIID string `env:"DISK_SCSI_ID" envDefault:"scsi0"`
|
||||
}
|
||||
|
||||
type GPUConfig struct {
|
||||
EnableGPU bool
|
||||
GPUBDF string `env:"GPU_BDF" envDefault:""`
|
||||
PCIeRootPort string `env:"GPU_PCIE_ROOT_PORT" envDefault:"pci.1"`
|
||||
PCIeBus string `env:"GPU_PCIE_BUS" envDefault:"pcie.0"`
|
||||
FWCfgPciMmio string `env:"GPU_FW_CFG_MMIO_MB" envDefault:"262144"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
EnableSEVSNP bool
|
||||
EnableTDX bool
|
||||
EnableDisk bool `env:"ENABLE_DISK" envDefault:"false"`
|
||||
QemuBinPath string `env:"BIN_PATH" envDefault:"qemu-system-x86_64"`
|
||||
UseSudo bool `env:"USE_SUDO" envDefault:"false"`
|
||||
|
||||
@@ -96,8 +114,11 @@ type Config struct {
|
||||
NetDevConfig
|
||||
VirtioNetPciConfig
|
||||
|
||||
// disk
|
||||
DiskImgConfig
|
||||
// disk config
|
||||
DiskConfig
|
||||
|
||||
// kernel and initramfs
|
||||
KernelConfig
|
||||
|
||||
// SEV-SNP
|
||||
SEVSNPConfig
|
||||
@@ -108,6 +129,9 @@ type Config struct {
|
||||
// vTPM
|
||||
IGVMConfig
|
||||
|
||||
// GPU passthrough
|
||||
GPUConfig
|
||||
|
||||
// display
|
||||
NoGraphic bool `env:"NO_GRAPHIC" envDefault:"true"`
|
||||
Monitor string `env:"MONITOR" envDefault:"pty"`
|
||||
@@ -123,6 +147,25 @@ type Config struct {
|
||||
EnvMount string `env:"ENV_MOUNT" envDefault:""`
|
||||
}
|
||||
|
||||
func (config Config) ValidateBootConfig() error {
|
||||
if config.EnableDisk {
|
||||
if strings.TrimSpace(config.DiskConfig.DstFile) == "" {
|
||||
return fmt.Errorf("disk boot enabled but destination disk image is not set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.TrimSpace(config.KernelConfig.KernelFile) == "" {
|
||||
return fmt.Errorf("kernel boot enabled but kernel image is not set")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(config.KernelConfig.RootFsFile) == "" {
|
||||
return fmt.Errorf("kernel boot enabled but initramfs image is not set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (config Config) ConstructQemuArgs() []string {
|
||||
args := []string{}
|
||||
|
||||
@@ -179,6 +222,39 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
config.VirtioNetPciConfig.Addr,
|
||||
config.VirtioNetPciConfig.ROMFile))
|
||||
|
||||
if config.EnableDisk {
|
||||
// disk image
|
||||
args = append(args, "-drive",
|
||||
fmt.Sprintf("file=%s,if=none,id=%s,format=%s",
|
||||
config.DiskConfig.DstFile,
|
||||
config.DiskConfig.ID,
|
||||
config.DiskConfig.Format))
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("virtio-scsi-pci,id=%s,disable-legacy=on,iommu_platform=true",
|
||||
config.DiskConfig.SCSIID))
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("scsi-hd,drive=%s,bus=%s.0",
|
||||
config.DiskConfig.ID,
|
||||
config.DiskConfig.SCSIID))
|
||||
}
|
||||
|
||||
// GPU passthrough via VFIO
|
||||
if config.GPUConfig.EnableGPU {
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("pcie-root-port,id=%s,bus=%s",
|
||||
config.GPUConfig.PCIeRootPort,
|
||||
config.GPUConfig.PCIeBus))
|
||||
|
||||
args = append(args, "-device",
|
||||
fmt.Sprintf("vfio-pci,host=%s,bus=%s",
|
||||
config.GPUConfig.GPUBDF,
|
||||
config.GPUConfig.PCIeRootPort))
|
||||
|
||||
args = append(args, "-fw_cfg",
|
||||
fmt.Sprintf("name=opt/ovmf/X-PciMmio64Mb,string=%s",
|
||||
config.GPUConfig.FWCfgPciMmio))
|
||||
}
|
||||
|
||||
// SEV-SNP
|
||||
if config.EnableSEVSNP {
|
||||
sevSnpType := "sev-snp-guest"
|
||||
@@ -233,9 +309,11 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
args = append(args, "-nodefaults")
|
||||
}
|
||||
|
||||
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
|
||||
args = append(args, "-append", config.KernelCommandLine)
|
||||
args = append(args, "-initrd", config.DiskImgConfig.RootFsFile)
|
||||
if !config.EnableDisk {
|
||||
args = append(args, "-kernel", config.KernelConfig.KernelFile)
|
||||
args = append(args, "-append", config.KernelCommandLine)
|
||||
args = append(args, "-initrd", config.KernelConfig.RootFsFile)
|
||||
}
|
||||
|
||||
// display
|
||||
if config.NoGraphic {
|
||||
@@ -267,5 +345,13 @@ func NewConfig() (*Config, error) {
|
||||
cfg.EnableSEVSNP = SEVSNPEnabledOnHost()
|
||||
cfg.EnableTDX = TDXEnabledOnHost()
|
||||
|
||||
bdf, detected := GPUPassthroughAvailable()
|
||||
if cfg.GPUConfig.GPUBDF != "" {
|
||||
cfg.GPUConfig.EnableGPU = true
|
||||
} else if detected {
|
||||
cfg.GPUConfig.EnableGPU = true
|
||||
cfg.GPUConfig.GPUBDF = bdf
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
+226
-2
@@ -51,7 +51,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
DiskImgConfig: DiskImgConfig{
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
@@ -115,7 +115,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
DiskImgConfig: DiskImgConfig{
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
@@ -151,6 +151,79 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
"-monitor", "pty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GPU passthrough configuration",
|
||||
config: Config{
|
||||
QemuBinPath: "qemu-system-x86_64",
|
||||
EnableKVM: true,
|
||||
Machine: "q35",
|
||||
CPU: "EPYC",
|
||||
SMPCount: 4,
|
||||
MaxCPUs: 64,
|
||||
MemID: "ram1",
|
||||
MemoryConfig: MemoryConfig{
|
||||
Size: "2048M",
|
||||
Slots: 5,
|
||||
Max: "30G",
|
||||
},
|
||||
OVMFCodeConfig: OVMFCodeConfig{
|
||||
If: "pflash",
|
||||
Format: "raw",
|
||||
Unit: 0,
|
||||
File: "/usr/share/OVMF/OVMF_CODE.fd",
|
||||
ReadOnly: "on",
|
||||
},
|
||||
OVMFVarsConfig: OVMFVarsConfig{
|
||||
If: "pflash",
|
||||
Format: "raw",
|
||||
Unit: 1,
|
||||
File: "/usr/share/OVMF/OVMF_VARS.fd",
|
||||
},
|
||||
NetDevConfig: NetDevConfig{
|
||||
ID: "vmnic",
|
||||
HostFwdAgent: 7020,
|
||||
GuestFwdAgent: 7002,
|
||||
},
|
||||
VirtioNetPciConfig: VirtioNetPciConfig{
|
||||
DisableLegacy: "on",
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
GPUConfig: GPUConfig{
|
||||
EnableGPU: true,
|
||||
GPUBDF: "0000:02:00.0",
|
||||
PCIeRootPort: "pci.1",
|
||||
PCIeBus: "pcie.0",
|
||||
FWCfgPciMmio: "262144",
|
||||
},
|
||||
KernelCommandLine: "quiet console=null",
|
||||
NoGraphic: true,
|
||||
Monitor: "pty",
|
||||
},
|
||||
expected: []string{
|
||||
"-enable-kvm",
|
||||
"-machine", "q35",
|
||||
"-cpu", "EPYC",
|
||||
"-smp", "4,maxcpus=64",
|
||||
"-m", "2048M,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=/usr/share/OVMF/OVMF_VARS.fd",
|
||||
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
|
||||
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
|
||||
"-device", "pcie-root-port,id=pci.1,bus=pcie.0",
|
||||
"-device", "vfio-pci,host=0000:02:00.0,bus=pci.1",
|
||||
"-fw_cfg", "name=opt/ovmf/X-PciMmio64Mb,string=262144",
|
||||
"-kernel", "img/bzImage",
|
||||
"-append", "quiet console=null",
|
||||
"-initrd", "img/rootfs.cpio.gz",
|
||||
"-nographic",
|
||||
"-monitor", "pty",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -194,3 +267,154 @@ func TestConstructQemuArgs_HostData(t *testing.T) {
|
||||
t.Errorf("ConstructQemuArgs() did not contain expected SEV-SNP configuration with host data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_TDX(t *testing.T) {
|
||||
config := Config{
|
||||
EnableKVM: true,
|
||||
EnableTDX: true,
|
||||
Machine: "q35",
|
||||
CPU: "EPYC",
|
||||
SMPCount: 4,
|
||||
MaxCPUs: 64,
|
||||
MemID: "ram1",
|
||||
MemoryConfig: MemoryConfig{
|
||||
Size: "4096M",
|
||||
Slots: 8,
|
||||
Max: "64G",
|
||||
},
|
||||
NetDevConfig: NetDevConfig{
|
||||
ID: "vmnic",
|
||||
HostFwdAgent: 7020,
|
||||
GuestFwdAgent: 7002,
|
||||
},
|
||||
VirtioNetPciConfig: VirtioNetPciConfig{
|
||||
DisableLegacy: "on",
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
TDXConfig: TDXConfig{
|
||||
ID: "tdx0",
|
||||
QuoteGenerationPort: 4050,
|
||||
OVMF: "/usr/share/ovmf/OVMF.fd",
|
||||
},
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
KernelCommandLine: "quiet console=null",
|
||||
NoGraphic: true,
|
||||
Monitor: "pty",
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"-enable-kvm",
|
||||
"-machine", "q35",
|
||||
"-cpu", "EPYC",
|
||||
"-smp", "4,maxcpus=64",
|
||||
"-m", "4096M,slots=8,maxmem=64G",
|
||||
"-netdev", "user,id=vmnic,hostfwd=tcp::7020-:7002",
|
||||
"-device", "virtio-net-pci,disable-legacy=on,iommu_platform=true,netdev=vmnic,addr=0x2,romfile=",
|
||||
"-object", "{\"qom-type\":\"tdx-guest\",\"id\":\"tdx0\",\"quote-generation-socket\":{\"type\": \"vsock\", \"cid\":\"2\",\"port\":\"4050\"}}",
|
||||
"-machine", "confidential-guest-support=tdx0,memory-backend=ram1,hpet=off",
|
||||
"-object", "memory-backend-memfd,id=ram1,size=4096M,share=true,prealloc=false",
|
||||
"-bios", "/usr/share/ovmf/OVMF.fd",
|
||||
"-nodefaults",
|
||||
"-kernel", "img/bzImage",
|
||||
"-append", "quiet console=null",
|
||||
"-initrd", "img/rootfs.cpio.gz",
|
||||
"-nographic",
|
||||
"-monitor", "pty",
|
||||
}
|
||||
|
||||
result := config.ConstructQemuArgs()
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf("ConstructQemuArgs() = %v, want %v", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_DiskBootSkipsKernelAndInitrd(t *testing.T) {
|
||||
config := Config{
|
||||
EnableKVM: true,
|
||||
EnableDisk: true,
|
||||
Machine: "q35",
|
||||
CPU: "EPYC",
|
||||
SMPCount: 4,
|
||||
MaxCPUs: 64,
|
||||
MemID: "ram1",
|
||||
MemoryConfig: MemoryConfig{
|
||||
Size: "2048M",
|
||||
Slots: 5,
|
||||
Max: "30G",
|
||||
},
|
||||
NetDevConfig: NetDevConfig{
|
||||
ID: "vmnic",
|
||||
HostFwdAgent: 7020,
|
||||
GuestFwdAgent: 7002,
|
||||
},
|
||||
VirtioNetPciConfig: VirtioNetPciConfig{
|
||||
DisableLegacy: "on",
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
DiskConfig: DiskConfig{
|
||||
DstFile: "img/disk.img",
|
||||
ID: "disk0",
|
||||
Format: "qcow2",
|
||||
SCSIID: "scsi0",
|
||||
},
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
NoGraphic: true,
|
||||
Monitor: "pty",
|
||||
}
|
||||
|
||||
result := config.ConstructQemuArgs()
|
||||
|
||||
for _, forbidden := range []string{"-kernel", "-append", "-initrd"} {
|
||||
for _, arg := range result {
|
||||
if arg == forbidden {
|
||||
t.Fatalf("ConstructQemuArgs() unexpectedly contained %s during disk boot: %v", forbidden, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstructQemuArgs_EnableDisk(t *testing.T) {
|
||||
config := Config{
|
||||
EnableDisk: true,
|
||||
DiskConfig: DiskConfig{
|
||||
SrcFile: "img/enc_os.qcow2",
|
||||
DstFile: "img/enc_os_dst.qcow2",
|
||||
ID: "disk0",
|
||||
Format: "qcow2",
|
||||
SCSIID: "scsi0",
|
||||
},
|
||||
}
|
||||
|
||||
result := config.ConstructQemuArgs()
|
||||
|
||||
expected := []string{
|
||||
"-drive", "file=img/enc_os_dst.qcow2,if=none,id=disk0,format=qcow2",
|
||||
"-device", "virtio-scsi-pci,id=scsi0,disable-legacy=on,iommu_platform=true",
|
||||
"-device", "scsi-hd,drive=disk0,bus=scsi0.0",
|
||||
}
|
||||
|
||||
var found []bool = make([]bool, len(expected))
|
||||
for i, arg := range result {
|
||||
for j := 0; j < len(expected); j += 2 {
|
||||
if arg == expected[j] && i+1 < len(result) && result[i+1] == expected[j+1] {
|
||||
found[j] = true
|
||||
found[j+1] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for j, f := range found {
|
||||
if !f {
|
||||
t.Errorf("ConstructQemuArgs() did not contain expected disk configuration: %s", expected[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+127
-6
@@ -3,10 +3,12 @@
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -18,12 +20,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
firmwareVars = "OVMF_VARS"
|
||||
KernelFile = "bzImage"
|
||||
rootfsFile = "rootfs.cpio"
|
||||
tmpDir = "/tmp"
|
||||
interval = 5 * time.Second
|
||||
shutdownTimeout = 30 * time.Second
|
||||
firmwareVars = "OVMF_VARS"
|
||||
KernelFile = "bzImage"
|
||||
rootfsFile = "rootfs.cpio"
|
||||
tmpDir = "/tmp"
|
||||
diskDstName = "cvmDisk"
|
||||
interval = 5 * time.Second
|
||||
shutdownTimeout = 30 * time.Second
|
||||
encryptedPartitionSizeDeltaGB = 1
|
||||
sourceDiskFormat = "qcow2"
|
||||
)
|
||||
|
||||
type VMInfo struct {
|
||||
@@ -39,6 +44,10 @@ type qemuVM struct {
|
||||
vm.StateMachine
|
||||
}
|
||||
|
||||
type qemuInfo struct {
|
||||
VirtualSize int64 `json:"virtual-size"`
|
||||
}
|
||||
|
||||
func NewVM(config any, cvmId string, logger *slog.Logger) vm.VM {
|
||||
return &qemuVM{
|
||||
vmi: config.(VMInfo),
|
||||
@@ -75,6 +84,44 @@ func (v *qemuVM) Start() (err error) {
|
||||
v.vmi.Config.OVMFVarsConfig.File = dstFile
|
||||
}
|
||||
|
||||
if v.vmi.Config.EnableDisk {
|
||||
srcDiskFile, err := filepath.Abs(v.vmi.Config.SrcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sizeGB, err := GetVirtualSizeGB(srcDiskFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstDiskFile := fmt.Sprintf("%s/%s-%s.%s", tmpDir, diskDstName, id, v.vmi.Config.DiskConfig.Format)
|
||||
sizeArg := fmt.Sprintf("%dG", sizeGB+encryptedPartitionSizeDeltaGB)
|
||||
|
||||
cmd := exec.Command(
|
||||
"qemu-img",
|
||||
"convert",
|
||||
"-f", sourceDiskFormat,
|
||||
"-O", v.vmi.Config.DiskConfig.Format,
|
||||
srcDiskFile,
|
||||
dstDiskFile,
|
||||
)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("qemu-img convert failed: %w: %s", err, string(out))
|
||||
}
|
||||
|
||||
cmd = exec.Command(
|
||||
"qemu-img",
|
||||
"resize",
|
||||
dstDiskFile,
|
||||
sizeArg,
|
||||
)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("qemu-img resize failed: %w: %s", err, string(out))
|
||||
}
|
||||
v.vmi.Config.DstFile = dstDiskFile
|
||||
}
|
||||
|
||||
exe, args, err := v.executableAndArgs()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,6 +158,14 @@ func (v *qemuVM) Stop() error {
|
||||
}
|
||||
}
|
||||
|
||||
if v.vmi.Config.EnableDisk {
|
||||
if v.vmi.Config.DstFile != "" {
|
||||
if err := os.RemoveAll(v.vmi.Config.DstFile); err != nil {
|
||||
return fmt.Errorf("failed to remove disk file: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := v.cmd.Process.Wait()
|
||||
@@ -156,6 +211,10 @@ func (v *qemuVM) executableAndArgs() (string, []string, error) {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if err := v.vmi.Config.ValidateBootConfig(); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
args := v.vmi.Config.ConstructQemuArgs()
|
||||
|
||||
if v.vmi.Config.UseSudo {
|
||||
@@ -231,3 +290,65 @@ func TDXEnabledOnHost() bool {
|
||||
|
||||
return TDXEnabled(string(cpuinfo), string(kernelParam))
|
||||
}
|
||||
|
||||
func GetVirtualSizeBytes(path string) (int64, error) {
|
||||
cmd := exec.Command("qemu-img", "info", "--output=json", path)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("qemu-img info failed: %w", err)
|
||||
}
|
||||
|
||||
var info qemuInfo
|
||||
if err := json.Unmarshal(out, &info); err != nil {
|
||||
return 0, fmt.Errorf("failed to parse qemu-img JSON: %w", err)
|
||||
}
|
||||
|
||||
if info.VirtualSize <= 0 {
|
||||
return 0, fmt.Errorf("invalid virtual size: %d", info.VirtualSize)
|
||||
}
|
||||
|
||||
return info.VirtualSize, nil
|
||||
}
|
||||
|
||||
func GetVirtualSizeGB(path string) (int, error) {
|
||||
bytes, err := GetVirtualSizeBytes(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
gb := (bytes + (1<<30 - 1)) >> 30
|
||||
return int(gb), nil
|
||||
}
|
||||
|
||||
// GPUPassthroughAvailable scans for NVIDIA GPU devices bound to the vfio-pci
|
||||
// driver and returns the BDF of the first one found.
|
||||
func GPUPassthroughAvailable() (string, bool) {
|
||||
const vfioPCIPath = "/sys/bus/pci/drivers/vfio-pci"
|
||||
entries, err := os.ReadDir(vfioPCIPath)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
bdf := entry.Name()
|
||||
if !strings.Contains(bdf, ":") {
|
||||
continue
|
||||
}
|
||||
|
||||
vendor, err := os.ReadFile(fmt.Sprintf("/sys/bus/pci/devices/%s/vendor", bdf))
|
||||
if err != nil || strings.TrimSpace(string(vendor)) != "0x10de" {
|
||||
continue
|
||||
}
|
||||
|
||||
class, err := os.ReadFile(fmt.Sprintf("/sys/bus/pci/devices/%s/class", bdf))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
classStr := strings.TrimSpace(string(class))
|
||||
// 0x0302xx = 3D Controller (e.g. H100), 0x0300xx = VGA Compatible Controller
|
||||
if strings.HasPrefix(classStr, "0x0302") || strings.HasPrefix(classStr, "0x0300") {
|
||||
return bdf, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user