mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-08-07 15:26:26 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91b1ab46db | |||
| 477c2c8390 | |||
| 12b3b61e8b | |||
| 1d71f9fb88 | |||
| 913295ba68 | |||
| 7b586cc5ac | |||
| 586e3b9a7c | |||
| 1f264aede5 | |||
| a5c00849a1 | |||
| f8a1646208 | |||
| a8548560de | |||
| ad964516a0 | |||
| 3e8f471569 | |||
| 2c9b1d3525 | |||
| e20d7d2e0b | |||
| efbaace2c1 | |||
| cbf917b9f5 | |||
| de18266bb4 | |||
| 076410f0e8 | |||
| 8662019452 | |||
| 08430a1b68 | |||
| 16a93e889e | |||
| 6ea0ac0781 | |||
| 9e014b79ba | |||
| e22735e730 | |||
| 5619528be8 | |||
| 9fec059e9b | |||
| 2ec4c0b357 | |||
| 299e330c46 | |||
| c63bd6f1e9 | |||
| e8acade166 | |||
| 63b32a6a23 | |||
| b4425bf76e | |||
| 4c37cbba95 | |||
| b1ea361cd1 | |||
| 47df040492 | |||
| 10fa58916e | |||
| 59f9a88330 | |||
| f25ddfc3a7 | |||
| 4802fbaa50 | |||
| d857f31cdc | |||
| d30593ce4d | |||
| ae14cc2165 | |||
| 2b5308151e | |||
| 9a247a3ceb | |||
| 5bfc0a8921 | |||
| a0ad18b3a9 | |||
| 39ec96e9d0 | |||
| aaa01ab421 | |||
| 4e4ed16492 | |||
| ca74c3e14b | |||
| d181e7c00d | |||
| 4e106f2c8e | |||
| 04f07449a8 | |||
| 260ff9d330 | |||
| 24b526af74 | |||
| a1863917b9 | |||
| 6e3cea2081 | |||
| b3bbb591d3 | |||
| bfe490d9bb | |||
| 54d34bc8d3 | |||
| 2f1353bd76 | |||
| 951655c346 | |||
| d63f64afac | |||
| d81d67e73d | |||
| 5e566d53c1 | |||
| 47d13fe583 | |||
| 3fb1b79537 | |||
| f9f11ed183 | |||
| 5dcf7a5c0a | |||
| 28a852a138 | |||
| a03985b758 | |||
| 3abb9aad03 |
@@ -29,9 +29,9 @@ 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/magistrala.BuildTime=$(TIME)' \
|
||||
-X 'github.com/absmach/magistrala.Version=$(VERSION)' \
|
||||
-X 'github.com/absmach/magistrala.Commit=$(COMMIT)'" \
|
||||
-X 'github.com/absmach/supermq.BuildTime=$(TIME)' \
|
||||
-X 'github.com/absmach/supermq.Version=$(VERSION)' \
|
||||
-X 'github.com/absmach/supermq.Commit=$(COMMIT)'" \
|
||||
$(if $(filter 1,$(EMBED_ENABLED)),-tags "embed",) \
|
||||
-o ${BUILD_DIR}/cocos-$(1) ./cmd/$(1)
|
||||
endef
|
||||
|
||||
@@ -21,20 +21,12 @@ 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.
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent/events/mocks"
|
||||
|
||||
@@ -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.Azure, attestation.TDX:
|
||||
case attestation.SNP, attestation.VTPM, attestation.SNPvTPM, 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/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/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/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms/api/grpc/storage"
|
||||
@@ -178,9 +178,6 @@ func (client *CVMSClient) processIncomingMessage(ctx context.Context, req *cvms.
|
||||
switch mes := req.Message.(type) {
|
||||
case *cvms.ServerStreamMessage_RunReqChunks:
|
||||
return client.handleRunReqChunks(ctx, mes)
|
||||
case *cvms.ServerStreamMessage_RunReq:
|
||||
client.logger.Info("Starting computation execution from non-chunked run request", "computationId", mes.RunReq.Id, "name", mes.RunReq.Name)
|
||||
go client.executeRun(ctx, mes.RunReq)
|
||||
case *cvms.ServerStreamMessage_StopComputation:
|
||||
go client.handleStopComputation(ctx, mes)
|
||||
case *cvms.ServerStreamMessage_AgentStateReq:
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
|
||||
+43
-120
@@ -19,7 +19,7 @@ import (
|
||||
sync "sync"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent/algorithm"
|
||||
"github.com/ultravioletrs/cocos/agent/events"
|
||||
runnerpb "github.com/ultravioletrs/cocos/agent/runner"
|
||||
@@ -85,29 +85,6 @@ 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).
|
||||
@@ -416,7 +393,7 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
|
||||
"kbs_enabled", kbsEnabled)
|
||||
|
||||
// Check if algorithm should be downloaded from remote source
|
||||
if as.computation.Algorithm.Source != nil && kbsEnabled && as.computation.Algorithm.Source.URL != "" {
|
||||
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,
|
||||
@@ -501,8 +478,8 @@ func (as *agentService) downloadAlgorithmIfRemote(state statemachine.State) {
|
||||
as.algoReceived = true
|
||||
as.algoRequirements = res.Requirements // Store requirements for installation
|
||||
|
||||
// The initramfs may have already provisioned /cocos/datasets.
|
||||
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
// Create datasets directory
|
||||
if err := os.Mkdir(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)
|
||||
@@ -536,7 +513,7 @@ func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) {
|
||||
hasRemoteDatasets := false
|
||||
for _, d := range as.computation.Datasets {
|
||||
kbsEnabled := d.KBS != nil && d.KBS.Enabled
|
||||
if d.Source != nil && kbsEnabled && d.Source.URL != "" {
|
||||
if d.Source != nil && kbsEnabled {
|
||||
hasRemoteDatasets = true
|
||||
break
|
||||
}
|
||||
@@ -557,7 +534,7 @@ func (as *agentService) downloadDatasetsIfRemote(state statemachine.State) {
|
||||
kbsURL = d.KBS.URL
|
||||
}
|
||||
|
||||
if d.Source != nil && kbsEnabled && d.Source.URL != "" {
|
||||
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")
|
||||
@@ -944,7 +921,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
kbsURL = as.computation.Algorithm.KBS.URL
|
||||
}
|
||||
|
||||
if as.computation.Algorithm.Source != nil && kbsEnabled && as.computation.Algorithm.Source.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")
|
||||
@@ -957,19 +934,6 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
} else {
|
||||
// Use directly uploaded algorithm
|
||||
algoData = algo.Algorithm
|
||||
|
||||
if as.computation.Algorithm.Source != nil && as.computation.Algorithm.Source.Encrypted && kbsEnabled {
|
||||
as.logger.Info("directly uploaded algorithm is encrypted, retrieving key from KBS")
|
||||
key, err := as.getKeyFromKBS(ctx, kbsURL, as.computation.Algorithm.Source.KBSResourcePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve key from KBS for uploaded algorithm: %w", err)
|
||||
}
|
||||
decrypted, err := resource.DecryptData(algoData, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt uploaded algorithm: %w", err)
|
||||
}
|
||||
algoData = decrypted
|
||||
}
|
||||
}
|
||||
|
||||
hash := sha3.Sum256(algoData)
|
||||
@@ -1012,7 +976,7 @@ func (as *agentService) Algo(ctx context.Context, algo Algorithm) error {
|
||||
as.algoRequirements = algo.Requirements
|
||||
as.algoReceived = true
|
||||
|
||||
if err := ensureDir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
if err := os.Mkdir(algorithm.DatasetsDir, 0o755); err != nil {
|
||||
return fmt.Errorf("error creating datasets directory: %v", err)
|
||||
}
|
||||
|
||||
@@ -1045,7 +1009,7 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
|
||||
kbsURL = d.KBS.URL
|
||||
}
|
||||
|
||||
if d.Source != nil && kbsEnabled && d.Source.URL != "" {
|
||||
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")
|
||||
@@ -1064,85 +1028,44 @@ func (as *agentService) Data(ctx context.Context, dataset Dataset) error {
|
||||
if matchedIndex == -1 {
|
||||
datasetData = dataset.Dataset
|
||||
datasetFilename = dataset.Filename
|
||||
|
||||
index, ok := IndexFromContext(ctx)
|
||||
if ok {
|
||||
if index < 0 || index >= len(as.computation.Datasets) {
|
||||
return ErrUndeclaredDataset
|
||||
}
|
||||
if as.computation.Datasets[index].Filename != datasetFilename {
|
||||
return ErrFileNameMismatch
|
||||
}
|
||||
matchedIndex = index
|
||||
} else {
|
||||
matchedIndex = -1
|
||||
for i, d := range as.computation.Datasets {
|
||||
if d.Filename == datasetFilename {
|
||||
matchedIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if matchedIndex == -1 {
|
||||
return ErrUndeclaredDataset
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remoteIndex := -1
|
||||
for i, d := range as.computation.Datasets {
|
||||
if d.Filename == datasetFilename {
|
||||
remoteIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if remoteIndex == -1 {
|
||||
return ErrUndeclaredDataset
|
||||
}
|
||||
matchedIndex = remoteIndex
|
||||
}
|
||||
|
||||
d := as.computation.Datasets[matchedIndex]
|
||||
|
||||
kbsEnabled := d.KBS != nil && d.KBS.Enabled
|
||||
kbsURL := ""
|
||||
if d.KBS != nil {
|
||||
kbsURL = d.KBS.URL
|
||||
}
|
||||
if d.Source != nil && d.Source.Encrypted && kbsEnabled {
|
||||
as.logger.Info("directly uploaded dataset is encrypted, retrieving key from KBS", "filename", d.Filename)
|
||||
key, err := as.getKeyFromKBS(ctx, kbsURL, d.Source.KBSResourcePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve key from KBS for dataset %s: %w", d.Filename, err)
|
||||
}
|
||||
decrypted, err := resource.DecryptData(datasetData, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt dataset %s: %w", d.Filename, err)
|
||||
}
|
||||
datasetData = decrypted
|
||||
}
|
||||
|
||||
hash := sha3.Sum256(datasetData)
|
||||
if hash != d.Hash {
|
||||
return ErrHashMismatch
|
||||
|
||||
matched := false
|
||||
for i, d := range as.computation.Datasets {
|
||||
if hash == d.Hash {
|
||||
if d.Filename != "" && d.Filename != datasetFilename {
|
||||
return ErrFileNameMismatch
|
||||
}
|
||||
|
||||
as.computation.Datasets = slices.Delete(as.computation.Datasets, i, i+1)
|
||||
|
||||
if DecompressFromContext(ctx) {
|
||||
if err := internal.UnzipFromMemory(datasetData, algorithm.DatasetsDir); err != nil {
|
||||
return fmt.Errorf("error decompressing dataset: %v", err)
|
||||
}
|
||||
} else {
|
||||
f, err := os.Create(fmt.Sprintf("%s/%s", algorithm.DatasetsDir, datasetFilename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dataset file: %v", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(datasetData); err != nil {
|
||||
return fmt.Errorf("error writing dataset to file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
as.computation.Datasets = slices.Delete(as.computation.Datasets, matchedIndex, matchedIndex+1)
|
||||
|
||||
if DecompressFromContext(ctx) {
|
||||
if err := internal.UnzipFromMemory(datasetData, algorithm.DatasetsDir); err != nil {
|
||||
return fmt.Errorf("error decompressing dataset: %v", err)
|
||||
}
|
||||
} else {
|
||||
f, err := os.Create(fmt.Sprintf("%s/%s", algorithm.DatasetsDir, datasetFilename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dataset file: %v", err)
|
||||
}
|
||||
|
||||
if _, err := f.Write(datasetData); err != nil {
|
||||
return fmt.Errorf("error writing dataset to file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %v", err)
|
||||
}
|
||||
if !matched {
|
||||
return ErrUndeclaredDataset
|
||||
}
|
||||
|
||||
if len(as.computation.Datasets) == 0 {
|
||||
@@ -1222,7 +1145,7 @@ func (as *agentService) runComputation(state statemachine.State) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := ensureDir(algorithm.ResultsDir, 0o755); err != nil {
|
||||
if err := os.Mkdir(algorithm.ResultsDir, 0o755); err != nil {
|
||||
as.mu.Lock()
|
||||
as.runError = fmt.Errorf("error creating results directory: %s", err.Error())
|
||||
as.mu.Unlock()
|
||||
|
||||
+3
-91
@@ -18,8 +18,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -218,7 +218,7 @@ func TestData(t *testing.T) {
|
||||
{
|
||||
name: "Test dataset not declared in manifest",
|
||||
data: Dataset{
|
||||
Filename: "undeclared.csv",
|
||||
Filename: datasetFile,
|
||||
},
|
||||
err: ErrUndeclaredDataset,
|
||||
},
|
||||
@@ -1808,91 +1808,3 @@ func TestInferSourceType(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitComputation_Errors(t *testing.T) {
|
||||
sm := &smmocks.StateMachine{}
|
||||
sm.On("GetState").Return(ReceivingAlgorithm) // Not ReceivingManifest
|
||||
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
}
|
||||
|
||||
err := svc.InitComputation(context.Background(), Computation{})
|
||||
assert.ErrorIs(t, err, ErrStateNotReady)
|
||||
}
|
||||
|
||||
func TestAlgo_Errors(t *testing.T) {
|
||||
t.Run("state not ready", func(t *testing.T) {
|
||||
sm := &smmocks.StateMachine{}
|
||||
sm.On("GetState").Return(ReceivingManifest) // Not ReceivingAlgorithm
|
||||
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
}
|
||||
|
||||
err := svc.Algo(context.Background(), Algorithm{})
|
||||
assert.ErrorIs(t, err, ErrStateNotReady)
|
||||
})
|
||||
|
||||
t.Run("all manifest items received", func(t *testing.T) {
|
||||
sm := &smmocks.StateMachine{}
|
||||
sm.On("GetState").Return(ReceivingAlgorithm)
|
||||
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
algoReceived: true,
|
||||
}
|
||||
|
||||
err := svc.Algo(context.Background(), Algorithm{})
|
||||
assert.ErrorIs(t, err, ErrAllManifestItemsReceived)
|
||||
})
|
||||
|
||||
t.Run("undeclared algorithm", func(t *testing.T) {
|
||||
sm := &smmocks.StateMachine{}
|
||||
sm.On("GetState").Return(ReceivingAlgorithm)
|
||||
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
algoReceived: false,
|
||||
computation: Computation{
|
||||
Algorithm: nil, // Not declared
|
||||
},
|
||||
}
|
||||
|
||||
err := svc.Algo(context.Background(), Algorithm{})
|
||||
assert.ErrorIs(t, err, ErrUndeclaredAlgorithm)
|
||||
})
|
||||
}
|
||||
|
||||
func TestData_ErrorsExtra(t *testing.T) {
|
||||
t.Run("all manifest items received", func(t *testing.T) {
|
||||
sm := &smmocks.StateMachine{}
|
||||
sm.On("GetState").Return(ReceivingData)
|
||||
|
||||
svc := &agentService{
|
||||
sm: sm,
|
||||
computation: Computation{
|
||||
Datasets: nil, // length 0
|
||||
},
|
||||
}
|
||||
|
||||
err := svc.Data(context.Background(), Dataset{})
|
||||
assert.ErrorIs(t, err, ErrAllManifestItemsReceived)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnsureDir_Error(t *testing.T) {
|
||||
tmpFile, err := os.CreateTemp("", "ensureDirTest")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpFile.Name())
|
||||
tmpFile.Close()
|
||||
|
||||
// ensureDir should fail because the parent path is a file, not a directory
|
||||
err = ensureDir(filepath.Join(tmpFile.Name(), "subdir"), 0o755)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestKbsHTTPGet_Error(t *testing.T) {
|
||||
_, err := kbsHTTPGet(context.Background(), "%%")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
tpmAttest "github.com/google/go-tpm-tools/proto/attest"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
mgerrors "github.com/absmach/magistrala/pkg/errors"
|
||||
mgerrors "github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/agent/auth"
|
||||
|
||||
+5
-11
@@ -17,8 +17,8 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/absmach/certs/sdk"
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/prometheus"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/absmach/supermq/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, ccPlatform)
|
||||
attest, certSerialNumber, err := attestationFromCert(ctx, cvmGrpcConfig.ClientCert, svc)
|
||||
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, ccPlatform attestation.PlatformType) ([]byte, string, error) {
|
||||
func attestationFromCert(ctx context.Context, certFilePath string, svc agent.Service) ([]byte, string, error) {
|
||||
if certFilePath == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
@@ -328,9 +328,6 @@ 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
|
||||
@@ -338,7 +335,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, ccPlatform)
|
||||
attest, err := svc.Attestation(ctx, nonceSNP, nonceVTPM, attestation.SNPvTPM)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -357,9 +354,6 @@ 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,12 +33,6 @@ 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)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
logpb "github.com/ultravioletrs/cocos/agent/log"
|
||||
@@ -156,10 +156,8 @@ 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))
|
||||
@@ -190,7 +188,7 @@ func main() {
|
||||
logger.Error("[ATTESTATION-SERVICE] No provider configured!")
|
||||
}
|
||||
|
||||
if (ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM) && isDirectProvider {
|
||||
if ccPlatform == attestation.SNP || ccPlatform == attestation.SNPvTPM {
|
||||
if err := vtpm.FetchSEVCertificates(uint(cfg.Vmpl)); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to fetch certificates: %s", err))
|
||||
exitCode = 1
|
||||
@@ -320,13 +318,6 @@ 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")
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
mglog "github.com/absmach/supermq/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/magistrala/logger"
|
||||
mglog "github.com/absmach/supermq/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/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"
|
||||
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"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
|
||||
@@ -4,51 +4,48 @@ go 1.26.0
|
||||
|
||||
require (
|
||||
github.com/caarlos0/env/v11 v11.4.0
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/go-sev-guest v0.14.1
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20260605221019-34f07ec666c4
|
||||
github.com/google/go-tdx-guest v0.3.2-0.20241009005452-097ee70d0843
|
||||
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.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
|
||||
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
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.62.3
|
||||
github.com/absmach/magistrala v0.20.0
|
||||
cloud.google.com/go/storage v1.57.2
|
||||
github.com/absmach/supermq v0.19.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.4
|
||||
github.com/go-jose/go-jose/v4 v4.1.3
|
||||
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.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.19.0 // indirect
|
||||
cloud.google.com/go v0.121.6 // indirect
|
||||
cloud.google.com/go/auth v0.16.5 // 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.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
|
||||
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
|
||||
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
|
||||
@@ -56,7 +53,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.6.0 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
@@ -68,8 +65,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.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.21.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.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
|
||||
@@ -83,31 +80,33 @@ 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.1 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // 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.10.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // 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.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.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.yaml.in/yaml/v2 v2.4.3 // 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
|
||||
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
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/absmach/certs v0.18.5
|
||||
github.com/absmach/certs v0.18.2
|
||||
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
|
||||
@@ -118,11 +117,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.8
|
||||
github.com/google/go-tpm-tools v0.4.4
|
||||
github.com/google/go-tpm v0.9.6
|
||||
github.com/google/go-tpm-tools v0.4.7
|
||||
github.com/google/logger v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // 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
|
||||
@@ -131,15 +130,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.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // 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
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -1,45 +1,43 @@
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
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 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/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.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=
|
||||
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=
|
||||
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.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/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/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.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/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/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=
|
||||
@@ -60,6 +58,7 @@ 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=
|
||||
@@ -68,8 +67,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.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
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-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=
|
||||
@@ -82,12 +81,10 @@ 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.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
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/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=
|
||||
@@ -98,8 +95,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.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
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-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=
|
||||
@@ -133,14 +130,16 @@ 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.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-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-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=
|
||||
@@ -152,30 +151,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.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/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/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.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
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/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.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
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/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.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
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/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=
|
||||
@@ -204,16 +203,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.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
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/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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
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/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=
|
||||
@@ -244,8 +243,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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
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/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=
|
||||
@@ -256,6 +255,7 @@ 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.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.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.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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
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.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/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/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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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/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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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/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.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
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.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/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/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.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=
|
||||
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=
|
||||
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=
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
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"
|
||||
@@ -1,214 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -1,42 +0,0 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
###
|
||||
# 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
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,273 +0,0 @@
|
||||
#!/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}"
|
||||
@@ -1,7 +0,0 @@
|
||||
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.
|
||||
@@ -1,3 +0,0 @@
|
||||
# Private key must not be committed
|
||||
db.key
|
||||
db.crt
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/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} "$@"
|
||||
@@ -1,7 +0,0 @@
|
||||
# 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
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"key-providers": {
|
||||
"attestation-agent": {
|
||||
"grpc": "127.0.0.1:50011"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,3 +0,0 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos /etc/certs
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
[Unit]
|
||||
RequiresMountsFor=/etc/cocos
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
d /var/log/cocos 0755 root root -
|
||||
d /run/cocos 0755 root root -
|
||||
@@ -1,117 +0,0 @@
|
||||
# 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
|
||||
@@ -1,2 +0,0 @@
|
||||
name: COCOS
|
||||
desc: External buildroot tree for Cocos AI
|
||||
@@ -1 +0,0 @@
|
||||
include $(sort $(wildcard $(BR2_EXTERNAL_COCOS_PATH)/package/*/*.mk))
|
||||
@@ -1,13 +0,0 @@
|
||||
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.
|
||||
@@ -1,27 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,11 +0,0 @@
|
||||
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
|
||||
@@ -1,34 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,28 +0,0 @@
|
||||
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
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Setup permissions for attestation socket directory
|
||||
|
||||
mkdir -p /run/cocos
|
||||
chmod 755 /run/cocos
|
||||
@@ -1,37 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,13 +0,0 @@
|
||||
[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
|
||||
@@ -1,11 +0,0 @@
|
||||
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
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,3 +0,0 @@
|
||||
# CoCo Keyprovider Environment Variables
|
||||
COCO_KP_SOCKET=127.0.0.1:50011
|
||||
RUST_LOG=info
|
||||
@@ -1,34 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,25 +0,0 @@
|
||||
[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
|
||||
@@ -1,5 +0,0 @@
|
||||
config BR2_PACKAGE_COMPUTATION_RUNNER
|
||||
bool "computation-runner"
|
||||
select BR2_PACKAGE_LOG_FORWARDER
|
||||
help
|
||||
Cocos AI Computation Runner service.
|
||||
@@ -1,22 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,6 +0,0 @@
|
||||
config BR2_PACKAGE_EGRESS_PROXY
|
||||
bool "egress-proxy"
|
||||
help
|
||||
Cocos AI Egress Proxy Service.
|
||||
|
||||
https://github.com/ultravioletrs/cocos
|
||||
@@ -1,22 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,4 +0,0 @@
|
||||
config BR2_PACKAGE_INGRESS_PROXY
|
||||
bool "ingress-proxy"
|
||||
help
|
||||
Cocos Ingress Proxy service.
|
||||
@@ -1,22 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,4 +0,0 @@
|
||||
config BR2_PACKAGE_LOG_FORWARDER
|
||||
bool "log-forwarder"
|
||||
help
|
||||
Cocos AI Log Forwarder service.
|
||||
@@ -1,22 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# 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))
|
||||
@@ -1,6 +0,0 @@
|
||||
config BR2_PACKAGE_WASMEDGE
|
||||
bool "wasmedge"
|
||||
default y
|
||||
help
|
||||
Wasmedge is a standalone runtime for WebAssembly.
|
||||
https://wasmedge.org/docs/
|
||||
@@ -1,8 +0,0 @@
|
||||
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))
|
||||
@@ -1,34 +0,0 @@
|
||||
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.11
|
||||
git checkout 2025.08-rc3
|
||||
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,8 +22,5 @@ if [ ! -d "$WORK_DIR" ]; then
|
||||
mkdir -p $WORK_DIR
|
||||
fi
|
||||
|
||||
# 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
|
||||
# Resize the root file system to 100%
|
||||
mount -o remount,size=100% /
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[Unit]
|
||||
Description=Cocos AI agent
|
||||
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
|
||||
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
|
||||
Before=docker.service
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ultravioletrs/cocos/agent/cvms"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ package attestation
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
|
||||
+4
-36
@@ -49,12 +49,6 @@ 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 |
|
||||
@@ -118,20 +112,6 @@ 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
|
||||
@@ -227,7 +207,7 @@ nc -zv localhost 7020
|
||||
|
||||
#### Conclusion
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### OVMF
|
||||
|
||||
@@ -304,18 +284,6 @@ 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
|
||||
@@ -326,16 +294,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. When writable disk support is enabled, 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. 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=/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"}
|
||||
{"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"}
|
||||
```
|
||||
|
||||
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=/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
|
||||
/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
|
||||
```
|
||||
|
||||
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/magistrala"
|
||||
"github.com/absmach/supermq"
|
||||
"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", magistrala.Health(svcName, instanceID))
|
||||
r.Get("/health", supermq.Health(svcName, instanceID))
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return r
|
||||
|
||||
+6
-56
@@ -4,7 +4,6 @@ package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
@@ -49,7 +48,7 @@ type VirtioNetPciConfig struct {
|
||||
ROMFile string `env:"VIRTIO_NET_PCI_ROMFILE"`
|
||||
}
|
||||
|
||||
type KernelConfig struct {
|
||||
type DiskImgConfig struct {
|
||||
KernelFile string `env:"DISK_IMG_KERNEL_FILE" envDefault:"img/bzImage"`
|
||||
RootFsFile string `env:"DISK_IMG_ROOTFS_FILE" envDefault:"img/rootfs.cpio.gz"`
|
||||
}
|
||||
@@ -73,14 +72,6 @@ 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:""`
|
||||
@@ -92,7 +83,6 @@ type GPUConfig struct {
|
||||
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"`
|
||||
|
||||
@@ -114,11 +104,8 @@ type Config struct {
|
||||
NetDevConfig
|
||||
VirtioNetPciConfig
|
||||
|
||||
// disk config
|
||||
DiskConfig
|
||||
|
||||
// kernel and initramfs
|
||||
KernelConfig
|
||||
// disk
|
||||
DiskImgConfig
|
||||
|
||||
// SEV-SNP
|
||||
SEVSNPConfig
|
||||
@@ -147,25 +134,6 @@ 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{}
|
||||
|
||||
@@ -222,22 +190,6 @@ 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",
|
||||
@@ -309,11 +261,9 @@ func (config Config) ConstructQemuArgs() []string {
|
||||
args = append(args, "-nodefaults")
|
||||
}
|
||||
|
||||
if !config.EnableDisk {
|
||||
args = append(args, "-kernel", config.KernelConfig.KernelFile)
|
||||
args = append(args, "-append", config.KernelCommandLine)
|
||||
args = append(args, "-initrd", config.KernelConfig.RootFsFile)
|
||||
}
|
||||
args = append(args, "-kernel", config.DiskImgConfig.KernelFile)
|
||||
args = append(args, "-append", config.KernelCommandLine)
|
||||
args = append(args, "-initrd", config.DiskImgConfig.RootFsFile)
|
||||
|
||||
// display
|
||||
if config.NoGraphic {
|
||||
|
||||
+3
-154
@@ -51,7 +51,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
KernelConfig: KernelConfig{
|
||||
DiskImgConfig: DiskImgConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
@@ -115,7 +115,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
KernelConfig: KernelConfig{
|
||||
DiskImgConfig: DiskImgConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
@@ -189,7 +189,7 @@ func TestConstructQemuArgs(t *testing.T) {
|
||||
IOMMUPlatform: true,
|
||||
Addr: "0x2",
|
||||
},
|
||||
KernelConfig: KernelConfig{
|
||||
DiskImgConfig: DiskImgConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
@@ -267,154 +267,3 @@ 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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-94
@@ -3,12 +3,10 @@
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -20,15 +18,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
firmwareVars = "OVMF_VARS"
|
||||
KernelFile = "bzImage"
|
||||
rootfsFile = "rootfs.cpio"
|
||||
tmpDir = "/tmp"
|
||||
diskDstName = "cvmDisk"
|
||||
interval = 5 * time.Second
|
||||
shutdownTimeout = 30 * time.Second
|
||||
encryptedPartitionSizeDeltaGB = 1
|
||||
sourceDiskFormat = "qcow2"
|
||||
firmwareVars = "OVMF_VARS"
|
||||
KernelFile = "bzImage"
|
||||
rootfsFile = "rootfs.cpio"
|
||||
tmpDir = "/tmp"
|
||||
interval = 5 * time.Second
|
||||
shutdownTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
type VMInfo struct {
|
||||
@@ -44,10 +39,6 @@ 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),
|
||||
@@ -84,44 +75,6 @@ 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
|
||||
@@ -158,14 +111,6 @@ 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()
|
||||
@@ -211,10 +156,6 @@ 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 {
|
||||
@@ -291,35 +232,6 @@ 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) {
|
||||
|
||||
+3
-358
@@ -3,12 +3,9 @@
|
||||
package qemu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -18,31 +15,6 @@ import (
|
||||
|
||||
const testComputationID = "test-computation"
|
||||
|
||||
func cleanupStrayQcow2(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get working directory: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(filepath.Join(wd, "qcow2"))
|
||||
})
|
||||
}
|
||||
|
||||
func requireTempFile(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file %s: %v", path, err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("failed to close temp file %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewVM(t *testing.T) {
|
||||
config := VMInfo{Config: Config{}}
|
||||
|
||||
@@ -63,10 +35,6 @@ func TestStart(t *testing.T) {
|
||||
File: tmpFile.Name(),
|
||||
},
|
||||
QemuBinPath: "echo",
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
}}
|
||||
|
||||
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
|
||||
@@ -90,10 +58,6 @@ func TestStartSudo(t *testing.T) {
|
||||
},
|
||||
QemuBinPath: "echo",
|
||||
UseSudo: true,
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
}}
|
||||
|
||||
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
|
||||
@@ -105,136 +69,6 @@ func TestStartSudo(t *testing.T) {
|
||||
_ = vm.Stop()
|
||||
}
|
||||
|
||||
func TestStart_EnableDisk(t *testing.T) {
|
||||
cleanupStrayQcow2(t)
|
||||
|
||||
toolsDir := t.TempDir()
|
||||
convertLogFile := filepath.Join(toolsDir, "qemu-img-convert.log")
|
||||
resizeLogFile := filepath.Join(toolsDir, "qemu-img-resize.log")
|
||||
srcDiskFile := filepath.Join(toolsDir, "enc_os.qcow2")
|
||||
requireTempFile(t, srcDiskFile)
|
||||
|
||||
writeFakeExecutable(t, toolsDir, "qemu-img", fmt.Sprintf(`#!/bin/sh
|
||||
case "$1" in
|
||||
info)
|
||||
printf '%%s' '{"virtual-size":2147483648}'
|
||||
;;
|
||||
convert)
|
||||
printf '%%s\n' "$@" > %q
|
||||
dst="$7"
|
||||
: > "$dst"
|
||||
;;
|
||||
resize)
|
||||
printf '%%s\n' "$@" > %q
|
||||
;;
|
||||
*)
|
||||
echo "unexpected subcommand: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
`, convertLogFile, resizeLogFile))
|
||||
|
||||
writeFakeExecutable(t, toolsDir, "fake-qemu", `#!/bin/sh
|
||||
trap 'exit 0' TERM INT
|
||||
while :; do
|
||||
sleep 1
|
||||
done
|
||||
`)
|
||||
prependPath(t, toolsDir)
|
||||
|
||||
config := VMInfo{Config: Config{
|
||||
EnableTDX: true,
|
||||
EnableDisk: true,
|
||||
QemuBinPath: "fake-qemu",
|
||||
DiskConfig: DiskConfig{
|
||||
SrcFile: srcDiskFile,
|
||||
ID: "disk0",
|
||||
Format: "qcow2",
|
||||
SCSIID: "scsi0",
|
||||
},
|
||||
}}
|
||||
|
||||
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
|
||||
|
||||
err := vm.Start()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, vm.cmd)
|
||||
assert.Contains(t, vm.vmi.Config.DstFile, filepath.Join(tmpDir, diskDstName))
|
||||
_, err = os.Stat(vm.vmi.Config.DstFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
loggedArgs, err := os.ReadFile(convertLogFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{
|
||||
"convert",
|
||||
"-f",
|
||||
"qcow2",
|
||||
"-O",
|
||||
"qcow2",
|
||||
srcDiskFile,
|
||||
vm.vmi.Config.DstFile,
|
||||
}, strings.Fields(string(loggedArgs)))
|
||||
|
||||
loggedArgs, err = os.ReadFile(resizeLogFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{
|
||||
"resize",
|
||||
vm.vmi.Config.DstFile,
|
||||
"3G",
|
||||
}, strings.Fields(string(loggedArgs)))
|
||||
|
||||
err = vm.Stop()
|
||||
assert.NoError(t, err)
|
||||
_, err = os.Stat(vm.vmi.Config.DstFile)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestStart_EnableDiskCreateError(t *testing.T) {
|
||||
cleanupStrayQcow2(t)
|
||||
|
||||
toolsDir := t.TempDir()
|
||||
srcDiskFile := filepath.Join(toolsDir, "enc_os.qcow2")
|
||||
requireTempFile(t, srcDiskFile)
|
||||
|
||||
writeFakeExecutable(t, toolsDir, "qemu-img", `#!/bin/sh
|
||||
case "$1" in
|
||||
info)
|
||||
printf '%s' '{"virtual-size":2147483648}'
|
||||
;;
|
||||
convert)
|
||||
echo 'disk create failed' >&2
|
||||
exit 1
|
||||
;;
|
||||
resize)
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unexpected subcommand: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
`)
|
||||
prependPath(t, toolsDir)
|
||||
|
||||
config := VMInfo{Config: Config{
|
||||
EnableTDX: true,
|
||||
EnableDisk: true,
|
||||
QemuBinPath: "fake-qemu",
|
||||
DiskConfig: DiskConfig{
|
||||
SrcFile: srcDiskFile,
|
||||
},
|
||||
}}
|
||||
|
||||
vm := NewVM(config, testComputationID, slog.Default()).(*qemuVM)
|
||||
|
||||
err := vm.Start()
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "qemu-img convert failed")
|
||||
assert.ErrorContains(t, err, "disk create failed")
|
||||
assert.Nil(t, vm.cmd)
|
||||
}
|
||||
|
||||
func TestStop(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
cmd := exec.Command("echo", "test")
|
||||
@@ -268,42 +102,6 @@ func TestStop(t *testing.T) {
|
||||
StateMachine: sm,
|
||||
}
|
||||
|
||||
err = vm.Stop()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
t.Run("disk enable", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dst := filepath.Join(dir, "disk.qcow2")
|
||||
|
||||
f, err := os.Create(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("echo", "test")
|
||||
err = cmd.Start()
|
||||
assert.NoError(t, err)
|
||||
sm := new(mocks.StateMachine)
|
||||
sm.On("Transition", pkgmanager.StopComputationRun).Return(nil)
|
||||
|
||||
vm := &qemuVM{
|
||||
vmi: VMInfo{
|
||||
Config: Config{
|
||||
EnableDisk: true,
|
||||
DiskConfig: DiskConfig{
|
||||
DstFile: dst,
|
||||
},
|
||||
},
|
||||
},
|
||||
cmd: &exec.Cmd{
|
||||
Process: cmd.Process,
|
||||
},
|
||||
StateMachine: sm,
|
||||
}
|
||||
|
||||
err = vm.Stop()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
@@ -312,13 +110,7 @@ func TestStop(t *testing.T) {
|
||||
func TestSetProcess(t *testing.T) {
|
||||
vm := &qemuVM{
|
||||
vmi: VMInfo{
|
||||
Config: Config{
|
||||
QemuBinPath: "echo", // Use 'echo' as a dummy QEMU binary
|
||||
KernelConfig: KernelConfig{
|
||||
KernelFile: "img/bzImage",
|
||||
RootFsFile: "img/rootfs.cpio.gz",
|
||||
},
|
||||
},
|
||||
Config: Config{QemuBinPath: "echo"}, // Use 'echo' as a dummy QEMU binary
|
||||
},
|
||||
}
|
||||
|
||||
@@ -383,156 +175,9 @@ func TestTDXEnabled(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSEVSNPEnabledOnHost(t *testing.T) {
|
||||
cpuinfo, cpuErr := os.ReadFile("/proc/cpuinfo")
|
||||
kernelParam, kernelErr := os.ReadFile("/sys/module/kvm_amd/parameters/sev_snp")
|
||||
|
||||
expected := false
|
||||
if cpuErr == nil && kernelErr == nil {
|
||||
expected = SEVSNPEnabled(string(cpuinfo), string(kernelParam))
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, SEVSNPEnabledOnHost())
|
||||
assert.False(t, SEVSNPEnabledOnHost())
|
||||
}
|
||||
|
||||
func TestTDXEnabledOnHost(t *testing.T) {
|
||||
cpuinfo, cpuErr := os.ReadFile("/proc/cpuinfo")
|
||||
kernelParam, kernelErr := os.ReadFile("/sys/module/kvm_intel/parameters/tdx")
|
||||
|
||||
expected := false
|
||||
if cpuErr == nil && kernelErr == nil {
|
||||
expected = TDXEnabled(string(cpuinfo), string(kernelParam))
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, TDXEnabledOnHost())
|
||||
}
|
||||
|
||||
func TestGetVirtualSizeBytes_Success(t *testing.T) {
|
||||
cleanup := writeFakeQemuImg(t, `{"virtual-size":2147483648}`, 0) // 2 GiB
|
||||
defer cleanup()
|
||||
|
||||
got, err := GetVirtualSizeBytes("whatever.qcow2")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error, got %v", err)
|
||||
}
|
||||
if got != 2147483648 {
|
||||
t.Fatalf("expected 2147483648, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVirtualSizeBytes_CommandFailure(t *testing.T) {
|
||||
cleanup := writeFakeQemuImg(t, `{"virtual-size":2147483648}`, 1) // non-zero exit
|
||||
defer cleanup()
|
||||
|
||||
_, err := GetVirtualSizeBytes("whatever.qcow2")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "qemu-img info failed") {
|
||||
t.Fatalf("expected wrapped error to contain %q, got %q", "qemu-img info failed", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVirtualSizeBytes_InvalidJSON(t *testing.T) {
|
||||
cleanup := writeFakeQemuImg(t, `not-json`, 0)
|
||||
defer cleanup()
|
||||
|
||||
_, err := GetVirtualSizeBytes("whatever.qcow2")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "failed to parse qemu-img JSON") {
|
||||
t.Fatalf("expected error to contain %q, got %q", "failed to parse qemu-img JSON", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVirtualSizeBytes_InvalidVirtualSize(t *testing.T) {
|
||||
cleanup := writeFakeQemuImg(t, `{"virtual-size":0}`, 0)
|
||||
defer cleanup()
|
||||
|
||||
_, err := GetVirtualSizeBytes("whatever.qcow2")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "invalid virtual size") {
|
||||
t.Fatalf("expected error to contain %q, got %q", "invalid virtual size", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVirtualSizeGB_RoundsUp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
virtualSz int64
|
||||
wantGB int
|
||||
}{
|
||||
{"exact_1GiB", 1 << 30, 1},
|
||||
{"one_byte_over", (1 << 30) + 1, 2},
|
||||
{"just_under_2GiB", (2 << 30) - 1, 2},
|
||||
{"exact_2GiB", 2 << 30, 2},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cleanup := writeFakeQemuImg(t, fmt.Sprintf(`{"virtual-size":%d}`, tc.virtualSz), 0)
|
||||
defer cleanup()
|
||||
|
||||
got, err := GetVirtualSizeGB("whatever.qcow2")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error, got %v", err)
|
||||
}
|
||||
if got != tc.wantGB {
|
||||
t.Fatalf("expected %d, got %d", tc.wantGB, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func writeFakeQemuImg(t *testing.T, stdout string, exitCode int) func() {
|
||||
dir := t.TempDir()
|
||||
fake := filepath.Join(dir, "qemu-img")
|
||||
|
||||
script := fmt.Sprintf(`#!/bin/sh
|
||||
# Minimal fake for: qemu-img info --output=json <path>
|
||||
if [ "$1" != "info" ]; then
|
||||
echo "unexpected subcommand: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# always print provided stdout, even if empty
|
||||
printf '%s' %q
|
||||
exit %d
|
||||
`, stdout, stdout, exitCode)
|
||||
|
||||
if err := os.WriteFile(fake, []byte(script), 0o755); err != nil {
|
||||
t.Fatalf("failed to write fake qemu-img: %v", err)
|
||||
}
|
||||
|
||||
oldPath := os.Getenv("PATH")
|
||||
if err := os.Setenv("PATH", dir+string(os.PathListSeparator)+oldPath); err != nil {
|
||||
t.Fatalf("failed to set PATH: %v", err)
|
||||
}
|
||||
|
||||
return func() {
|
||||
_ = os.Setenv("PATH", oldPath)
|
||||
}
|
||||
}
|
||||
|
||||
func writeFakeExecutable(t *testing.T, dir, name, script string) {
|
||||
t.Helper()
|
||||
|
||||
path := filepath.Join(dir, name)
|
||||
if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
|
||||
t.Fatalf("failed to write fake executable %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func prependPath(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
|
||||
oldPath := os.Getenv("PATH")
|
||||
if err := os.Setenv("PATH", dir+string(os.PathListSeparator)+oldPath); err != nil {
|
||||
t.Fatalf("failed to set PATH: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.Setenv("PATH", oldPath)
|
||||
})
|
||||
assert.False(t, TDXEnabledOnHost())
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/google/uuid"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
mglog "github.com/absmach/supermq/logger"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
attestation "github.com/ultravioletrs/cocos/pkg/atls/eaattestation"
|
||||
)
|
||||
|
||||
func selfSignedCert(t testing.TB) tls.Certificate {
|
||||
func selfSignedCert(t *testing.T) tls.Certificate {
|
||||
t.Helper()
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
@@ -42,7 +42,7 @@ func selfSignedCert(t testing.TB) tls.Certificate {
|
||||
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: priv}
|
||||
}
|
||||
|
||||
func tlsPair(t testing.TB, cert tls.Certificate) (srv, cli *tls.Conn) {
|
||||
func tlsPair(t *testing.T, cert tls.Certificate) (srv, cli *tls.Conn) {
|
||||
t.Helper()
|
||||
srvConf := &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13}
|
||||
cliConf := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func selfSignedCert(t testing.TB) tls.Certificate {
|
||||
func selfSignedCert(t *testing.T) tls.Certificate {
|
||||
t.Helper()
|
||||
|
||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
)
|
||||
|
||||
@@ -32,27 +28,6 @@ func InitializeDefaultMAAVars(config *EnvConfig) {
|
||||
maa.OSType = config.OSType
|
||||
maa.OSDistro = config.OSDistro
|
||||
MaaURL = config.MaaURL
|
||||
InitializeDefaultAzureTDXVarsFromEnv()
|
||||
}
|
||||
|
||||
func InitializeDefaultAzureTDXVars(imdsURL string, hclRefreshDelay time.Duration) {
|
||||
if imdsURL = strings.TrimSpace(imdsURL); imdsURL != "" {
|
||||
azureTDXIMDSQuoteURL = imdsURL
|
||||
}
|
||||
if hclRefreshDelay >= 0 {
|
||||
azureTDXHCLRefreshDelay = hclRefreshDelay
|
||||
}
|
||||
}
|
||||
|
||||
func InitializeDefaultAzureTDXVarsFromEnv() {
|
||||
imdsURL := os.Getenv("AZURE_TDX_IMDS_URL")
|
||||
hclRefreshDelay := azureTDXHCLRefreshDelay
|
||||
if value := strings.TrimSpace(os.Getenv("AZURE_HCL_REFRESH_WAIT")); value != "" {
|
||||
if parsed, err := time.ParseDuration(value); err == nil {
|
||||
hclRefreshDelay = parsed
|
||||
}
|
||||
}
|
||||
InitializeDefaultAzureTDXVars(imdsURL, hclRefreshDelay)
|
||||
}
|
||||
|
||||
func (c *EnvConfig) InitializeOSVars(build, osType, osDistro string) {
|
||||
|
||||
@@ -5,7 +5,6 @@ package azure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -57,34 +56,3 @@ func TestInitializeOSVars(t *testing.T) {
|
||||
assert.Equal(t, "TypeY", cfg.OSType)
|
||||
assert.Equal(t, "DistroZ", cfg.OSDistro)
|
||||
}
|
||||
|
||||
func TestInitializeDefaultAzureTDXVars(t *testing.T) {
|
||||
oldURL := azureTDXIMDSQuoteURL
|
||||
oldDelay := azureTDXHCLRefreshDelay
|
||||
defer func() {
|
||||
azureTDXIMDSQuoteURL = oldURL
|
||||
azureTDXHCLRefreshDelay = oldDelay
|
||||
}()
|
||||
|
||||
InitializeDefaultAzureTDXVars(" https://imds.example/tdquote ", 1500*time.Millisecond)
|
||||
|
||||
assert.Equal(t, "https://imds.example/tdquote", azureTDXIMDSQuoteURL)
|
||||
assert.Equal(t, 1500*time.Millisecond, azureTDXHCLRefreshDelay)
|
||||
}
|
||||
|
||||
func TestInitializeDefaultAzureTDXVarsFromEnv(t *testing.T) {
|
||||
oldURL := azureTDXIMDSQuoteURL
|
||||
oldDelay := azureTDXHCLRefreshDelay
|
||||
defer func() {
|
||||
azureTDXIMDSQuoteURL = oldURL
|
||||
azureTDXHCLRefreshDelay = oldDelay
|
||||
}()
|
||||
|
||||
t.Setenv("AZURE_TDX_IMDS_URL", "https://env-imds.example/tdquote")
|
||||
t.Setenv("AZURE_HCL_REFRESH_WAIT", "2s")
|
||||
|
||||
InitializeDefaultAzureTDXVarsFromEnv()
|
||||
|
||||
assert.Equal(t, "https://env-imds.example/tdquote", azureTDXIMDSQuoteURL)
|
||||
assert.Equal(t, 2*time.Second, azureTDXHCLRefreshDelay)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/edgelesssys/go-azguestattestation/maa"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
@@ -52,10 +52,6 @@ func NewProvider() attestation.Provider {
|
||||
}
|
||||
|
||||
func (a provider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error) {
|
||||
if isAzureTDX() {
|
||||
return a.TeeAttestation(teeNonce)
|
||||
}
|
||||
|
||||
var tokenNonce [vtpm.Nonce]byte
|
||||
copy(tokenNonce[:], teeNonce)
|
||||
|
||||
@@ -81,10 +77,6 @@ func (a provider) Attestation(teeNonce []byte, vTpmNonce []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
func (a provider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
||||
if isAzureTDX() {
|
||||
return fetchAzureTDXQuote(teeNonce)
|
||||
}
|
||||
|
||||
var tokenNonce [vtpm.Nonce]byte
|
||||
copy(tokenNonce[:], teeNonce)
|
||||
|
||||
@@ -97,6 +89,7 @@ func (a provider) TeeAttestation(teeNonce []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (a provider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
|
||||
fmt.Printf("DEBUG: VTpmAttestation: vtpm.ExternalTPM is %T at %p\n", vtpm.ExternalTPM, &vtpm.ExternalTPM)
|
||||
quote, err := vtpm.FetchQuote(vTpmNonce)
|
||||
if err != nil {
|
||||
return []byte{}, errors.Wrap(vtpm.ErrFetchQuote, err)
|
||||
@@ -118,14 +111,6 @@ func (c *defaultMaaClient) Attest(ctx context.Context, nonce []byte, maaURL stri
|
||||
var DefaultMaaClient MaaClient = &defaultMaaClient{}
|
||||
|
||||
func (a provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
|
||||
if isAzureTDX() {
|
||||
token, err := FetchAzureTDXAttestationToken(tokenNonce, MaaURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrFetchAzureToken, err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
token, err := DefaultMaaClient.Attest(context.Background(), tokenNonce, MaaURL, http.DefaultClient)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ErrFetchAzureToken, err)
|
||||
@@ -167,19 +152,12 @@ func (v verifier) VerifyEAT(eatToken []byte, teeNonce []byte, vTpmNonce []byte)
|
||||
func (v verifier) VerifyWithCoRIM(report []byte, manifest *corim.UnsignedCorim) error {
|
||||
attestation := &attest.Attestation{}
|
||||
if err := proto.Unmarshal(report, attestation); err != nil {
|
||||
tdxErr := verifyTDXQuoteWithCoRIM(report, manifest)
|
||||
if tdxErr == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to unmarshal attestation report: %w; Azure TDX verification failed: %v", err, tdxErr)
|
||||
return fmt.Errorf("failed to unmarshal attestation report: %w", err)
|
||||
}
|
||||
|
||||
// Extract measurement from SEV-SNP report if present
|
||||
snpRep := attestation.GetSevSnpAttestation()
|
||||
if snpRep == nil {
|
||||
if tdxErr := verifyTDXQuoteWithCoRIM(report, manifest); tdxErr == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no SEV-SNP attestation found in report")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,683 +0,0 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
tdxabi "github.com/google/go-tdx-guest/abi"
|
||||
tdxpb "github.com/google/go-tdx-guest/proto/tdx"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
"github.com/veraison/corim/comid"
|
||||
"github.com/veraison/corim/corim"
|
||||
)
|
||||
|
||||
const (
|
||||
tdxAttestEndpoint = "attest/TdxVm"
|
||||
tdxAPIVersion = "2025-06-01"
|
||||
tdxRuntimeBinary = "Binary"
|
||||
tdxRuntimeJSON = "JSON"
|
||||
|
||||
azureHCLReportNVIndex = 0x01400001
|
||||
azureHCLReportDataNVIndex = 0x01400002
|
||||
|
||||
azureHCLSignature = "HCLA"
|
||||
azureHCLVersion = 2
|
||||
azureHCLRequestType = 2
|
||||
azureHCLRuntimeDataVersion = 1
|
||||
azureHCLHashSHA256 = 1
|
||||
azureHCLReportTypeSNP = 2
|
||||
azureHCLReportTypeTDX = 4
|
||||
|
||||
azureHCLHeaderSize = 0x20
|
||||
azureHCLMaxHWReportSize = 0x4a0
|
||||
azureHCLRuntimeDataOffset = azureHCLHeaderSize + azureHCLMaxHWReportSize
|
||||
azureHCLRuntimeClaimsOffset = 0x14
|
||||
azureTDReportSize = 0x400
|
||||
azureTDReportDataOffset = 0x80
|
||||
)
|
||||
|
||||
var (
|
||||
azureTDXHCLReportReader = readAzureHCLReport
|
||||
azureTDXReportDataWriter = writeAzureTDXReportData
|
||||
azureTDXHCLRefreshDelay = 3 * time.Second
|
||||
azureTDXIMDSQuoteURL = "http://169.254.169.254/acc/tdquote"
|
||||
)
|
||||
|
||||
// TDXQuoteFetcher fetches a raw TDX quote for the provided REPORT_DATA.
|
||||
type TDXQuoteFetcher interface {
|
||||
FetchQuote(reportData [tdxabi.ReportDataSize]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type TDXEvidenceFetcher interface {
|
||||
FetchEvidence(reportData [tdxabi.ReportDataSize]byte) (*azureTDXEvidence, error)
|
||||
}
|
||||
|
||||
type defaultTDXQuoteFetcher struct{}
|
||||
|
||||
func (f defaultTDXQuoteFetcher) FetchQuote(reportData [tdxabi.ReportDataSize]byte) ([]byte, error) {
|
||||
evidence, err := f.FetchEvidence(reportData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return evidence.Quote, nil
|
||||
}
|
||||
|
||||
func (f defaultTDXQuoteFetcher) FetchEvidence(reportData [tdxabi.ReportDataSize]byte) (*azureTDXEvidence, error) {
|
||||
hclReport, err := readFreshAzureTDXHCLReport(reportData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedReport, err := parseAzureHCLReport(hclReport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsedReport.reportType != azureHCLReportTypeTDX {
|
||||
return nil, fmt.Errorf("Azure HCL report is not TDX")
|
||||
}
|
||||
if err := validateAzureTDXRuntimeClaimsHash(parsedReport); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quote, err := DefaultAzureTDXIMDSClient.GetQuote(context.Background(), parsedReport.hwReport, http.DefaultClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get Azure TDX quote from IMDS: %w", err)
|
||||
}
|
||||
|
||||
return &azureTDXEvidence{
|
||||
Quote: quote,
|
||||
RuntimeData: append([]byte(nil), parsedReport.runtimeClaims...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DefaultTDXQuoteFetcher is used by the Azure TDX provider and is replaceable in tests.
|
||||
var DefaultTDXQuoteFetcher TDXQuoteFetcher = defaultTDXQuoteFetcher{}
|
||||
|
||||
// AzureTDXIMDSClient fetches an Azure TDX quote from the Azure Instance Metadata Service.
|
||||
type AzureTDXIMDSClient interface {
|
||||
GetQuote(ctx context.Context, tdReport []byte, client *http.Client) ([]byte, error)
|
||||
}
|
||||
|
||||
type defaultAzureTDXIMDSClient struct{}
|
||||
|
||||
// DefaultAzureTDXIMDSClient is used by the Azure TDX quote fetcher and is replaceable in tests.
|
||||
var DefaultAzureTDXIMDSClient AzureTDXIMDSClient = &defaultAzureTDXIMDSClient{}
|
||||
|
||||
// AzureTDXClient submits Azure TDX VM attestation evidence to Microsoft Azure Attestation.
|
||||
type AzureTDXClient interface {
|
||||
AttestTDXVM(ctx context.Context, quote []byte, runtimeData []byte, nonce []byte, maaURL string, client *http.Client) (string, error)
|
||||
}
|
||||
|
||||
type defaultAzureTDXClient struct{}
|
||||
|
||||
// DefaultAzureTDXClient is used by Azure TDX token fetching and is replaceable in tests.
|
||||
var DefaultAzureTDXClient AzureTDXClient = &defaultAzureTDXClient{}
|
||||
|
||||
type tdxAttestRequest struct {
|
||||
Quote string `json:"quote"`
|
||||
RuntimeData *tdxDataBlob `json:"runtimeData,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
}
|
||||
|
||||
type tdxDataBlob struct {
|
||||
Data string `json:"data"`
|
||||
DataType string `json:"dataType"`
|
||||
}
|
||||
|
||||
type tdxAttestResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type tdxIMDSQuoteRequest struct {
|
||||
Report string `json:"report"`
|
||||
}
|
||||
|
||||
type tdxIMDSQuoteResponse struct {
|
||||
Quote string `json:"quote"`
|
||||
}
|
||||
|
||||
type azureTDXEvidence struct {
|
||||
Quote []byte
|
||||
RuntimeData []byte
|
||||
}
|
||||
|
||||
type azureHCLReport struct {
|
||||
reportType uint32
|
||||
hashType uint32
|
||||
hwReport []byte
|
||||
runtimeClaims []byte
|
||||
}
|
||||
|
||||
func isAzureTDX() bool {
|
||||
if azureTDXHCLReportReader == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hclReport, err := azureTDXHCLReportReader()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
parsedReport, err := parseAzureHCLReport(hclReport)
|
||||
return err == nil && parsedReport.reportType == azureHCLReportTypeTDX
|
||||
}
|
||||
|
||||
func fetchAzureTDXQuote(teeNonce []byte) ([]byte, error) {
|
||||
if teeNonce == nil {
|
||||
return nil, fmt.Errorf("tee nonce is required for Azure TDX attestation")
|
||||
}
|
||||
if len(teeNonce) != tdxabi.ReportDataSize {
|
||||
return nil, fmt.Errorf("invalid tee nonce length: expected %d bytes, got %d bytes", tdxabi.ReportDataSize, len(teeNonce))
|
||||
}
|
||||
|
||||
var reportData [tdxabi.ReportDataSize]byte
|
||||
copy(reportData[:], teeNonce)
|
||||
|
||||
evidence, err := fetchAzureTDXEvidence(reportData, teeNonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return evidence.Quote, nil
|
||||
}
|
||||
|
||||
func (c *defaultAzureTDXClient) AttestTDXVM(ctx context.Context, quote []byte, runtimeData []byte, nonce []byte, maaURL string, client *http.Client) (string, error) {
|
||||
if maaURL == "" {
|
||||
return "", fmt.Errorf("maaURL is empty")
|
||||
}
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
maaURL, err := url.JoinPath(maaURL, tdxAttestEndpoint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing maaURL: %w", err)
|
||||
}
|
||||
maaURL += fmt.Sprintf("?api-version=%s", tdxAPIVersion)
|
||||
|
||||
attestRequest := tdxAttestRequest{
|
||||
Quote: base64.RawURLEncoding.EncodeToString(quote),
|
||||
}
|
||||
if len(runtimeData) > 0 {
|
||||
attestRequest.RuntimeData = &tdxDataBlob{
|
||||
Data: base64.RawURLEncoding.EncodeToString(runtimeData),
|
||||
DataType: tdxRuntimeDataType(runtimeData),
|
||||
}
|
||||
}
|
||||
if len(nonce) > 0 {
|
||||
attestRequest.Nonce = base64.RawURLEncoding.EncodeToString(nonce)
|
||||
}
|
||||
|
||||
reqBytes, err := json.Marshal(attestRequest)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshaling TDX attestation request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, maaURL, bytes.NewReader(reqBytes))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating TDX attestation request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("doing TDX attestation request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if msg, err := io.ReadAll(resp.Body); err == nil && len(msg) > 0 {
|
||||
return "", fmt.Errorf("MAA returned %v: %s", resp.Status, msg)
|
||||
}
|
||||
return "", fmt.Errorf("MAA returned %v", resp.Status)
|
||||
}
|
||||
|
||||
var attestResponse tdxAttestResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&attestResponse); err != nil {
|
||||
return "", fmt.Errorf("decoding TDX attestation response: %w", err)
|
||||
}
|
||||
if attestResponse.Token == "" {
|
||||
return "", fmt.Errorf("azure TDX attestation token not found in response")
|
||||
}
|
||||
|
||||
return attestResponse.Token, nil
|
||||
}
|
||||
|
||||
func (c *defaultAzureTDXIMDSClient) GetQuote(ctx context.Context, tdReport []byte, client *http.Client) ([]byte, error) {
|
||||
if len(tdReport) != azureTDReportSize {
|
||||
return nil, fmt.Errorf("invalid TD report length: expected %d bytes, got %d bytes", azureTDReportSize, len(tdReport))
|
||||
}
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
quoteRequest := tdxIMDSQuoteRequest{
|
||||
Report: base64.RawURLEncoding.EncodeToString(tdReport),
|
||||
}
|
||||
|
||||
reqBytes, err := json.Marshal(quoteRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling Azure TDX IMDS quote request: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, azureTDXIMDSQuoteURL, bytes.NewReader(reqBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating Azure TDX IMDS quote request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("doing Azure TDX IMDS quote request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if msg, err := io.ReadAll(resp.Body); err == nil && len(msg) > 0 {
|
||||
return nil, fmt.Errorf("Azure TDX IMDS returned %v: %s", resp.Status, msg)
|
||||
}
|
||||
return nil, fmt.Errorf("Azure TDX IMDS returned %v", resp.Status)
|
||||
}
|
||||
|
||||
var quoteResponse tdxIMDSQuoteResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode("eResponse); err != nil {
|
||||
return nil, fmt.Errorf("decoding Azure TDX IMDS quote response: %w", err)
|
||||
}
|
||||
if quoteResponse.Quote == "" {
|
||||
return nil, fmt.Errorf("Azure TDX IMDS quote not found in response")
|
||||
}
|
||||
|
||||
quote, err := decodeBase64URL(quoteResponse.Quote)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding Azure TDX IMDS quote: %w", err)
|
||||
}
|
||||
|
||||
return quote, nil
|
||||
}
|
||||
|
||||
// FetchAzureTDXAttestationToken fetches an Azure Attestation token for an Azure TDX VM.
|
||||
func FetchAzureTDXAttestationToken(tokenNonce []byte, maaURL string) ([]byte, error) {
|
||||
reportData := tdxReportDataFromRuntimeData(tokenNonce)
|
||||
evidence, err := fetchAzureTDXEvidence(reportData, tokenNonce)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch Azure TDX quote: %w", err)
|
||||
}
|
||||
|
||||
token, err := DefaultAzureTDXClient.AttestTDXVM(context.Background(), evidence.Quote, evidence.RuntimeData, tokenNonce, maaURL, http.DefaultClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching azure TDX token: %w", err)
|
||||
}
|
||||
|
||||
return []byte(token), nil
|
||||
}
|
||||
|
||||
func tdxReportDataFromRuntimeData(runtimeData []byte) [tdxabi.ReportDataSize]byte {
|
||||
hash := sha256.Sum256(runtimeData)
|
||||
var reportData [tdxabi.ReportDataSize]byte
|
||||
copy(reportData[:sha256.Size], hash[:])
|
||||
return reportData
|
||||
}
|
||||
|
||||
func fetchAzureTDXEvidence(reportData [tdxabi.ReportDataSize]byte, fallbackRuntimeData []byte) (*azureTDXEvidence, error) {
|
||||
if evidenceFetcher, ok := DefaultTDXQuoteFetcher.(TDXEvidenceFetcher); ok {
|
||||
return evidenceFetcher.FetchEvidence(reportData)
|
||||
}
|
||||
|
||||
quote, err := DefaultTDXQuoteFetcher.FetchQuote(reportData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &azureTDXEvidence{
|
||||
Quote: quote,
|
||||
RuntimeData: append([]byte(nil), fallbackRuntimeData...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readFreshAzureTDXHCLReport(reportData [tdxabi.ReportDataSize]byte) ([]byte, error) {
|
||||
if azureTDXReportDataWriter != nil {
|
||||
if err := azureTDXReportDataWriter(reportData[:]); err != nil {
|
||||
return nil, fmt.Errorf("writing Azure TDX report data: %w", err)
|
||||
}
|
||||
if azureTDXHCLRefreshDelay > 0 {
|
||||
time.Sleep(azureTDXHCLRefreshDelay)
|
||||
}
|
||||
}
|
||||
if azureTDXHCLReportReader == nil {
|
||||
return nil, fmt.Errorf("Azure TDX HCL report reader is not configured")
|
||||
}
|
||||
|
||||
hclReport, err := azureTDXHCLReportReader()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading Azure TDX HCL report: %w", err)
|
||||
}
|
||||
|
||||
return hclReport, nil
|
||||
}
|
||||
|
||||
func readAzureHCLReport() ([]byte, error) {
|
||||
tpm, err := tpm2.OpenTPM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tpm.Close()
|
||||
|
||||
return tpm2.NVReadEx(tpm, azureHCLReportNVIndex, tpm2.HandleOwner, "", 0)
|
||||
}
|
||||
|
||||
func writeAzureTDXReportData(data []byte) error {
|
||||
if len(data) != tdxabi.ReportDataSize {
|
||||
return fmt.Errorf("invalid Azure TDX report data length: expected %d bytes, got %d bytes", tdxabi.ReportDataSize, len(data))
|
||||
}
|
||||
|
||||
tpm, err := tpm2.OpenTPM()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tpm.Close()
|
||||
|
||||
return writeAzureTDXReportDataToTPM(tpm, data)
|
||||
}
|
||||
|
||||
func writeAzureTDXReportDataToTPM(tpm io.ReadWriter, data []byte) error {
|
||||
if len(data) > int(^uint16(0)) {
|
||||
return fmt.Errorf("Azure TDX report data is too large")
|
||||
}
|
||||
|
||||
if err := ensureAzureTDXReportDataIndex(tpm, uint16(len(data))); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tpm2.NVWrite(tpm, tpm2.HandleOwner, azureHCLReportDataNVIndex, "", tpmutil.U16Bytes(data), 0); err != nil {
|
||||
return fmt.Errorf("writing Azure TDX report-data NV index: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureAzureTDXReportDataIndex(tpm io.ReadWriter, size uint16) error {
|
||||
pub, err := tpm2.NVReadPublic(tpm, azureHCLReportDataNVIndex)
|
||||
if err == nil {
|
||||
if pub.DataSize == size {
|
||||
return nil
|
||||
}
|
||||
if err := tpm2.NVUndefineSpace(tpm, "", tpm2.HandleOwner, azureHCLReportDataNVIndex); err != nil {
|
||||
return fmt.Errorf("undefining mismatched Azure TDX report-data NV index: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
nvPub := tpm2.NVPublic{
|
||||
NVIndex: azureHCLReportDataNVIndex,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.AttrOwnerWrite | tpm2.AttrOwnerRead,
|
||||
DataSize: size,
|
||||
}
|
||||
authArea := tpm2.AuthCommand{
|
||||
Session: tpm2.HandlePasswordSession,
|
||||
Attributes: tpm2.AttrContinueSession,
|
||||
Auth: []byte(""),
|
||||
}
|
||||
if err := tpm2.NVDefineSpaceEx(tpm, tpm2.HandleOwner, "", nvPub, authArea); err != nil {
|
||||
return fmt.Errorf("defining Azure TDX report-data NV index: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseAzureHCLReport(report []byte) (*azureHCLReport, error) {
|
||||
minSize := azureHCLRuntimeDataOffset + azureHCLRuntimeClaimsOffset
|
||||
if len(report) < minSize {
|
||||
return nil, fmt.Errorf("invalid Azure HCL report size: expected at least %d bytes, got %d bytes", minSize, len(report))
|
||||
}
|
||||
if string(report[:len(azureHCLSignature)]) != azureHCLSignature {
|
||||
return nil, fmt.Errorf("invalid Azure HCL report signature")
|
||||
}
|
||||
if version := binary.LittleEndian.Uint32(report[4:8]); version != azureHCLVersion {
|
||||
return nil, fmt.Errorf("invalid Azure HCL report version: expected %d, got %d", azureHCLVersion, version)
|
||||
}
|
||||
reportSize := binary.LittleEndian.Uint32(report[8:12])
|
||||
if reportSize > uint32(len(report)) {
|
||||
return nil, fmt.Errorf("invalid Azure HCL report size: header reports %d bytes, got %d bytes", reportSize, len(report))
|
||||
}
|
||||
if requestType := binary.LittleEndian.Uint32(report[12:16]); requestType != azureHCLRequestType {
|
||||
return nil, fmt.Errorf("invalid Azure HCL report request type: expected %d, got %d", azureHCLRequestType, requestType)
|
||||
}
|
||||
|
||||
runtimeData := report[azureHCLRuntimeDataOffset:]
|
||||
dataSize := binary.LittleEndian.Uint32(runtimeData[0:4])
|
||||
if dataSize < azureHCLRuntimeClaimsOffset {
|
||||
return nil, fmt.Errorf("invalid Azure HCL runtime data size: %d", dataSize)
|
||||
}
|
||||
if azureHCLRuntimeDataOffset+int(dataSize) > len(report) {
|
||||
return nil, fmt.Errorf("invalid Azure HCL runtime data size: header reports %d bytes", dataSize)
|
||||
}
|
||||
if version := binary.LittleEndian.Uint32(runtimeData[4:8]); version != azureHCLRuntimeDataVersion {
|
||||
return nil, fmt.Errorf("invalid Azure HCL runtime data version: expected %d, got %d", azureHCLRuntimeDataVersion, version)
|
||||
}
|
||||
|
||||
reportType := binary.LittleEndian.Uint32(runtimeData[8:12])
|
||||
if reportType != azureHCLReportTypeSNP && reportType != azureHCLReportTypeTDX {
|
||||
return nil, fmt.Errorf("invalid Azure HCL report type: %d", reportType)
|
||||
}
|
||||
|
||||
hashType := binary.LittleEndian.Uint32(runtimeData[12:16])
|
||||
claimsSize := binary.LittleEndian.Uint32(runtimeData[16:20])
|
||||
claimsEnd := azureHCLRuntimeClaimsOffset + int(claimsSize)
|
||||
if claimsEnd > int(dataSize) {
|
||||
return nil, fmt.Errorf("invalid Azure HCL runtime claims size: %d", claimsSize)
|
||||
}
|
||||
|
||||
hwReportSize := azureHCLMaxHWReportSize
|
||||
if reportType == azureHCLReportTypeTDX {
|
||||
hwReportSize = azureTDReportSize
|
||||
}
|
||||
if azureHCLHeaderSize+hwReportSize > len(report) {
|
||||
return nil, fmt.Errorf("invalid Azure HCL hardware report size: %d", hwReportSize)
|
||||
}
|
||||
|
||||
return &azureHCLReport{
|
||||
reportType: reportType,
|
||||
hashType: hashType,
|
||||
hwReport: append([]byte(nil), report[azureHCLHeaderSize:azureHCLHeaderSize+hwReportSize]...),
|
||||
runtimeClaims: append([]byte(nil), runtimeData[azureHCLRuntimeClaimsOffset:claimsEnd]...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateAzureTDXRuntimeClaimsHash(report *azureHCLReport) error {
|
||||
if report.hashType != azureHCLHashSHA256 {
|
||||
return fmt.Errorf("unsupported Azure HCL runtime data hash type: %d", report.hashType)
|
||||
}
|
||||
if len(report.hwReport) < azureTDReportDataOffset+sha256.Size {
|
||||
return fmt.Errorf("invalid Azure TDX TD report size: %d", len(report.hwReport))
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(report.runtimeClaims)
|
||||
if !bytes.Equal(hash[:], report.hwReport[azureTDReportDataOffset:azureTDReportDataOffset+sha256.Size]) {
|
||||
return fmt.Errorf("Azure TDX runtime claims hash does not match TD report data")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tdxRuntimeDataType(runtimeData []byte) string {
|
||||
if json.Valid(runtimeData) {
|
||||
return tdxRuntimeJSON
|
||||
}
|
||||
return tdxRuntimeBinary
|
||||
}
|
||||
|
||||
func decodeBase64URL(value string) ([]byte, error) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(value)
|
||||
if err == nil {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
return base64.URLEncoding.DecodeString(value)
|
||||
}
|
||||
|
||||
func verifyTDXQuoteWithCoRIM(report []byte, manifest *corim.UnsignedCorim) error {
|
||||
decodedQuote, err := tdxabi.QuoteToProto(report)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse TDX quote: %w", err)
|
||||
}
|
||||
|
||||
var mrtd []byte
|
||||
switch q := decodedQuote.(type) {
|
||||
case *tdxpb.QuoteV4:
|
||||
tdReport := q.GetTdQuoteBody()
|
||||
if tdReport == nil {
|
||||
return fmt.Errorf("missing TDX quote body")
|
||||
}
|
||||
mrtd = tdReport.GetMrTd()
|
||||
case *tdxpb.QuoteV5:
|
||||
bodyDesc := q.GetTdQuoteBodyDescriptor()
|
||||
if bodyDesc == nil {
|
||||
return fmt.Errorf("missing TDX quote body descriptor")
|
||||
}
|
||||
tdReport := bodyDesc.GetTdQuoteBodyV5()
|
||||
if tdReport == nil {
|
||||
return fmt.Errorf("missing TDX quote body V5")
|
||||
}
|
||||
mrtd = tdReport.GetMrTd()
|
||||
default:
|
||||
return fmt.Errorf("unsupported TDX quote format")
|
||||
}
|
||||
|
||||
if len(mrtd) == 0 {
|
||||
return fmt.Errorf("no MRTD in TDX quote")
|
||||
}
|
||||
|
||||
if err := matchMeasurementInCoRIM(manifest, mrtd); err != nil {
|
||||
return fmt.Errorf("%w for Azure TDX", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchMeasurementInCoRIM(manifest *corim.UnsignedCorim, measurement []byte) error {
|
||||
if manifest == nil || len(manifest.Tags) == 0 {
|
||||
return fmt.Errorf("no tags in CoRIM")
|
||||
}
|
||||
|
||||
for _, tag := range manifest.Tags {
|
||||
if !bytes.HasPrefix(tag, corim.ComidTag) {
|
||||
continue
|
||||
}
|
||||
|
||||
tagValue := tag[len(corim.ComidTag):]
|
||||
|
||||
var c comid.Comid
|
||||
if err := c.FromCBOR(tagValue); err != nil {
|
||||
return fmt.Errorf("failed to parse CoMID: %w", err)
|
||||
}
|
||||
|
||||
if c.Triples.ReferenceValues == nil {
|
||||
continue
|
||||
}
|
||||
for _, rv := range *c.Triples.ReferenceValues {
|
||||
for _, m := range rv.Measurements {
|
||||
if m.Val.Digests == nil {
|
||||
continue
|
||||
}
|
||||
for _, digest := range *m.Val.Digests {
|
||||
if bytes.Equal(digest.HashValue, measurement) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("no matching reference value found in CoRIM")
|
||||
}
|
||||
|
||||
// AzureTDXMeasurementData contains the fields extracted from an Azure TDX attestation token
|
||||
// needed to construct a CoRIM policy for the TDX platform.
|
||||
type AzureTDXMeasurementData struct {
|
||||
MRTD string
|
||||
MRSEAM string
|
||||
RTMRs []string
|
||||
SEAMSVN uint64
|
||||
}
|
||||
|
||||
// ExtractAzureTDXMeasurement extracts core TDX measurements from an Azure Attestation token.
|
||||
func ExtractAzureTDXMeasurement(token string) (*AzureTDXMeasurementData, error) {
|
||||
claims, err := DefaultValidator.Validate(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate token: %w", err)
|
||||
}
|
||||
|
||||
mrtd, ok := azureClaimString(claims, "tdx_mrtd")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get MRTD from claims")
|
||||
}
|
||||
|
||||
mrSeam, _ := azureClaimString(claims, "tdx_mrseam")
|
||||
|
||||
rtmrs := make([]string, 0, 4)
|
||||
for _, name := range []string{"tdx_rtmr0", "tdx_rtmr1", "tdx_rtmr2", "tdx_rtmr3"} {
|
||||
if value, ok := azureClaimString(claims, name); ok {
|
||||
rtmrs = append(rtmrs, value)
|
||||
}
|
||||
}
|
||||
|
||||
seamSVN, _ := azureClaimUint64(claims, "tdx_seamsvn")
|
||||
|
||||
return &AzureTDXMeasurementData{
|
||||
MRTD: mrtd,
|
||||
MRSEAM: mrSeam,
|
||||
RTMRs: rtmrs,
|
||||
SEAMSVN: seamSVN,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func azureClaimString(claims map[string]any, name string) (string, bool) {
|
||||
if value, ok := claims[name].(string); ok {
|
||||
return value, true
|
||||
}
|
||||
|
||||
tee, ok := claims["x-ms-isolation-tee"].(map[string]any)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
value, ok := tee[name].(string)
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func azureClaimUint64(claims map[string]any, name string) (uint64, bool) {
|
||||
value, ok := claims[name]
|
||||
if !ok {
|
||||
tee, teeOK := claims["x-ms-isolation-tee"].(map[string]any)
|
||||
if !teeOK {
|
||||
return 0, false
|
||||
}
|
||||
value, ok = tee[name]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
return uint64(typed), true
|
||||
case int:
|
||||
return uint64(typed), true
|
||||
case uint64:
|
||||
return typed, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
@@ -1,455 +0,0 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
tdxabi "github.com/google/go-tdx-guest/abi"
|
||||
tdxpb "github.com/google/go-tdx-guest/proto/tdx"
|
||||
tdxtestdata "github.com/google/go-tdx-guest/testing/testdata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/veraison/corim/comid"
|
||||
"github.com/veraison/corim/corim"
|
||||
"github.com/veraison/swid"
|
||||
)
|
||||
|
||||
type mockTDXQuoteFetcher struct {
|
||||
quote []byte
|
||||
err error
|
||||
gotReportData [tdxabi.ReportDataSize]byte
|
||||
fetchQuoteCall bool
|
||||
}
|
||||
|
||||
func (m *mockTDXQuoteFetcher) FetchQuote(reportData [tdxabi.ReportDataSize]byte) ([]byte, error) {
|
||||
m.fetchQuoteCall = true
|
||||
m.gotReportData = reportData
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
return m.quote, nil
|
||||
}
|
||||
|
||||
type mockTDXEvidenceFetcher struct {
|
||||
evidence *azureTDXEvidence
|
||||
err error
|
||||
gotReportData [tdxabi.ReportDataSize]byte
|
||||
fetchEvidenceHit bool
|
||||
}
|
||||
|
||||
func (m *mockTDXEvidenceFetcher) FetchQuote(reportData [tdxabi.ReportDataSize]byte) ([]byte, error) {
|
||||
evidence, err := m.FetchEvidence(reportData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return evidence.Quote, nil
|
||||
}
|
||||
|
||||
func (m *mockTDXEvidenceFetcher) FetchEvidence(reportData [tdxabi.ReportDataSize]byte) (*azureTDXEvidence, error) {
|
||||
m.fetchEvidenceHit = true
|
||||
m.gotReportData = reportData
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
return m.evidence, nil
|
||||
}
|
||||
|
||||
type mockAzureTDXClient struct {
|
||||
token string
|
||||
err error
|
||||
gotQuote []byte
|
||||
gotRuntime []byte
|
||||
gotNonce []byte
|
||||
gotMaaURL string
|
||||
attestCalls int
|
||||
}
|
||||
|
||||
func (m *mockAzureTDXClient) AttestTDXVM(_ context.Context, quote []byte, runtimeData []byte, nonce []byte, maaURL string, _ *http.Client) (string, error) {
|
||||
m.attestCalls++
|
||||
m.gotQuote = append([]byte(nil), quote...)
|
||||
m.gotRuntime = append([]byte(nil), runtimeData...)
|
||||
m.gotNonce = append([]byte(nil), nonce...)
|
||||
m.gotMaaURL = maaURL
|
||||
if m.err != nil {
|
||||
return "", m.err
|
||||
}
|
||||
return m.token, nil
|
||||
}
|
||||
|
||||
type mockAzureTDXIMDSClient struct {
|
||||
quote []byte
|
||||
err error
|
||||
gotTDReport []byte
|
||||
getQuoteCall bool
|
||||
}
|
||||
|
||||
func (m *mockAzureTDXIMDSClient) GetQuote(_ context.Context, tdReport []byte, _ *http.Client) ([]byte, error) {
|
||||
m.getQuoteCall = true
|
||||
m.gotTDReport = append([]byte(nil), tdReport...)
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
return m.quote, nil
|
||||
}
|
||||
|
||||
func testAzureHCLReport(reportType uint32, runtimeClaims []byte) []byte {
|
||||
reportSize := azureHCLRuntimeDataOffset + azureHCLRuntimeClaimsOffset + len(runtimeClaims)
|
||||
hclReport := make([]byte, reportSize)
|
||||
copy(hclReport[:len(azureHCLSignature)], azureHCLSignature)
|
||||
binary.LittleEndian.PutUint32(hclReport[4:8], azureHCLVersion)
|
||||
binary.LittleEndian.PutUint32(hclReport[8:12], uint32(reportSize))
|
||||
binary.LittleEndian.PutUint32(hclReport[12:16], azureHCLRequestType)
|
||||
|
||||
runtimeData := hclReport[azureHCLRuntimeDataOffset:]
|
||||
binary.LittleEndian.PutUint32(runtimeData[0:4], uint32(azureHCLRuntimeClaimsOffset+len(runtimeClaims)))
|
||||
binary.LittleEndian.PutUint32(runtimeData[4:8], azureHCLRuntimeDataVersion)
|
||||
binary.LittleEndian.PutUint32(runtimeData[8:12], reportType)
|
||||
binary.LittleEndian.PutUint32(runtimeData[12:16], azureHCLHashSHA256)
|
||||
binary.LittleEndian.PutUint32(runtimeData[16:20], uint32(len(runtimeClaims)))
|
||||
copy(runtimeData[azureHCLRuntimeClaimsOffset:], runtimeClaims)
|
||||
|
||||
if reportType == azureHCLReportTypeTDX {
|
||||
hash := sha256.Sum256(runtimeClaims)
|
||||
copy(hclReport[azureHCLHeaderSize+azureTDReportDataOffset:], hash[:])
|
||||
}
|
||||
|
||||
return hclReport
|
||||
}
|
||||
|
||||
func TestProvider_TeeAttestation_AzureTDX(t *testing.T) {
|
||||
oldReader := azureTDXHCLReportReader
|
||||
oldFetcher := DefaultTDXQuoteFetcher
|
||||
defer func() {
|
||||
azureTDXHCLReportReader = oldReader
|
||||
DefaultTDXQuoteFetcher = oldFetcher
|
||||
}()
|
||||
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return testAzureHCLReport(azureHCLReportTypeTDX, []byte(`{"keys":[]}`)), nil
|
||||
}
|
||||
fetcher := &mockTDXQuoteFetcher{quote: []byte("tdx-quote")}
|
||||
DefaultTDXQuoteFetcher = fetcher
|
||||
|
||||
reportData := bytes.Repeat([]byte{0xAB}, tdxabi.ReportDataSize)
|
||||
got, err := NewProvider().TeeAttestation(reportData)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("tdx-quote"), got)
|
||||
assert.True(t, fetcher.fetchQuoteCall)
|
||||
assert.Equal(t, reportData, fetcher.gotReportData[:])
|
||||
}
|
||||
|
||||
func TestProvider_TeeAttestation_AzureTDX_InvalidNonce(t *testing.T) {
|
||||
oldReader := azureTDXHCLReportReader
|
||||
defer func() { azureTDXHCLReportReader = oldReader }()
|
||||
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return testAzureHCLReport(azureHCLReportTypeTDX, []byte(`{"keys":[]}`)), nil
|
||||
}
|
||||
|
||||
_, err := NewProvider().TeeAttestation([]byte("short"))
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "invalid tee nonce length")
|
||||
}
|
||||
|
||||
func TestProvider_AzureAttestationToken_AzureTDX(t *testing.T) {
|
||||
oldReader := azureTDXHCLReportReader
|
||||
oldFetcher := DefaultTDXQuoteFetcher
|
||||
oldClient := DefaultAzureTDXClient
|
||||
oldMaaURL := MaaURL
|
||||
defer func() {
|
||||
azureTDXHCLReportReader = oldReader
|
||||
DefaultTDXQuoteFetcher = oldFetcher
|
||||
DefaultAzureTDXClient = oldClient
|
||||
MaaURL = oldMaaURL
|
||||
}()
|
||||
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return testAzureHCLReport(azureHCLReportTypeTDX, []byte(`{"keys":[]}`)), nil
|
||||
}
|
||||
MaaURL = "https://tdx.example.attest.azure.net"
|
||||
|
||||
fetcher := &mockTDXQuoteFetcher{quote: []byte("quote")}
|
||||
client := &mockAzureTDXClient{token: "tdx-token"}
|
||||
DefaultTDXQuoteFetcher = fetcher
|
||||
DefaultAzureTDXClient = client
|
||||
|
||||
nonce := []byte("token-nonce")
|
||||
got, err := NewProvider().AzureAttestationToken(nonce)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("tdx-token"), got)
|
||||
|
||||
expectedReportData := tdxReportDataFromRuntimeData(nonce)
|
||||
assert.Equal(t, expectedReportData, fetcher.gotReportData)
|
||||
assert.Equal(t, []byte("quote"), client.gotQuote)
|
||||
assert.Equal(t, nonce, client.gotRuntime)
|
||||
assert.Equal(t, nonce, client.gotNonce)
|
||||
assert.Equal(t, MaaURL, client.gotMaaURL)
|
||||
}
|
||||
|
||||
func TestFetchAzureTDXAttestationToken_UsesHCLRuntimeClaims(t *testing.T) {
|
||||
oldFetcher := DefaultTDXQuoteFetcher
|
||||
oldClient := DefaultAzureTDXClient
|
||||
defer func() {
|
||||
DefaultTDXQuoteFetcher = oldFetcher
|
||||
DefaultAzureTDXClient = oldClient
|
||||
}()
|
||||
|
||||
runtimeClaims := []byte(`{"keys":[],"user-data":"nonce"}`)
|
||||
fetcher := &mockTDXEvidenceFetcher{
|
||||
evidence: &azureTDXEvidence{
|
||||
Quote: []byte("quote"),
|
||||
RuntimeData: runtimeClaims,
|
||||
},
|
||||
}
|
||||
client := &mockAzureTDXClient{token: "tdx-token"}
|
||||
DefaultTDXQuoteFetcher = fetcher
|
||||
DefaultAzureTDXClient = client
|
||||
|
||||
nonce := []byte("token-nonce")
|
||||
got, err := FetchAzureTDXAttestationToken(nonce, "https://tdx.example.attest.azure.net")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("tdx-token"), got)
|
||||
assert.True(t, fetcher.fetchEvidenceHit)
|
||||
assert.Equal(t, tdxReportDataFromRuntimeData(nonce), fetcher.gotReportData)
|
||||
assert.Equal(t, runtimeClaims, client.gotRuntime)
|
||||
assert.Equal(t, nonce, client.gotNonce)
|
||||
}
|
||||
|
||||
func TestFetchAzureTDXAttestationToken_FetchQuoteError(t *testing.T) {
|
||||
oldFetcher := DefaultTDXQuoteFetcher
|
||||
defer func() { DefaultTDXQuoteFetcher = oldFetcher }()
|
||||
|
||||
DefaultTDXQuoteFetcher = &mockTDXQuoteFetcher{err: fmt.Errorf("quote unavailable")}
|
||||
|
||||
_, err := FetchAzureTDXAttestationToken([]byte("nonce"), "https://tdx.example.attest.azure.net")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to fetch Azure TDX quote")
|
||||
}
|
||||
|
||||
func TestDefaultTDXQuoteFetcher_FetchEvidence_AzureHCLIMDS(t *testing.T) {
|
||||
oldReader := azureTDXHCLReportReader
|
||||
oldWriter := azureTDXReportDataWriter
|
||||
oldDelay := azureTDXHCLRefreshDelay
|
||||
oldIMDSClient := DefaultAzureTDXIMDSClient
|
||||
defer func() {
|
||||
azureTDXHCLReportReader = oldReader
|
||||
azureTDXReportDataWriter = oldWriter
|
||||
azureTDXHCLRefreshDelay = oldDelay
|
||||
DefaultAzureTDXIMDSClient = oldIMDSClient
|
||||
}()
|
||||
|
||||
runtimeClaims := []byte(`{"keys":[],"user-data":"fresh"}`)
|
||||
hclReport := testAzureHCLReport(azureHCLReportTypeTDX, runtimeClaims)
|
||||
var gotReportData []byte
|
||||
azureTDXReportDataWriter = func(data []byte) error {
|
||||
gotReportData = append([]byte(nil), data...)
|
||||
return nil
|
||||
}
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return hclReport, nil
|
||||
}
|
||||
azureTDXHCLRefreshDelay = 0
|
||||
imdsClient := &mockAzureTDXIMDSClient{quote: []byte("tdx-quote")}
|
||||
DefaultAzureTDXIMDSClient = imdsClient
|
||||
|
||||
reportData := [tdxabi.ReportDataSize]byte{0xAB}
|
||||
evidence, err := defaultTDXQuoteFetcher{}.FetchEvidence(reportData)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, reportData[:], gotReportData)
|
||||
assert.Equal(t, []byte("tdx-quote"), evidence.Quote)
|
||||
assert.Equal(t, runtimeClaims, evidence.RuntimeData)
|
||||
assert.True(t, imdsClient.getQuoteCall)
|
||||
assert.Len(t, imdsClient.gotTDReport, azureTDReportSize)
|
||||
}
|
||||
|
||||
func TestDefaultAzureTDXClient_AttestTDXVM(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
assert.Equal(t, "/attest/TdxVm", r.URL.Path)
|
||||
assert.Equal(t, tdxAPIVersion, r.URL.Query().Get("api-version"))
|
||||
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
|
||||
var req tdxAttestRequest
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
|
||||
assert.Equal(t, base64.RawURLEncoding.EncodeToString([]byte("quote")), req.Quote)
|
||||
require.NotNil(t, req.RuntimeData)
|
||||
assert.Equal(t, base64.RawURLEncoding.EncodeToString([]byte("runtime")), req.RuntimeData.Data)
|
||||
assert.Equal(t, tdxRuntimeBinary, req.RuntimeData.DataType)
|
||||
assert.Equal(t, base64.RawURLEncoding.EncodeToString([]byte("nonce")), req.Nonce)
|
||||
|
||||
_, _ = w.Write([]byte(`{"token":"tdx-token"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
token, err := (&defaultAzureTDXClient{}).AttestTDXVM(
|
||||
context.Background(),
|
||||
[]byte("quote"),
|
||||
[]byte("runtime"),
|
||||
[]byte("nonce"),
|
||||
server.URL,
|
||||
server.Client(),
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "tdx-token", token)
|
||||
}
|
||||
|
||||
func TestDefaultAzureTDXClient_AttestTDXVM_JSONRuntimeData(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var req tdxAttestRequest
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
|
||||
require.NotNil(t, req.RuntimeData)
|
||||
assert.Equal(t, tdxRuntimeJSON, req.RuntimeData.DataType)
|
||||
|
||||
_, _ = w.Write([]byte(`{"token":"tdx-token"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
_, err := (&defaultAzureTDXClient{}).AttestTDXVM(
|
||||
context.Background(),
|
||||
[]byte("quote"),
|
||||
[]byte(`{"keys":[]}`),
|
||||
[]byte("nonce"),
|
||||
server.URL,
|
||||
server.Client(),
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDefaultAzureTDXClient_AttestTDXVM_ErrorStatus(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "bad quote", http.StatusBadRequest)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
_, err := (&defaultAzureTDXClient{}).AttestTDXVM(context.Background(), []byte("quote"), nil, nil, server.URL, server.Client())
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "MAA returned 400 Bad Request")
|
||||
}
|
||||
|
||||
func TestDefaultAzureTDXIMDSClient_GetQuote(t *testing.T) {
|
||||
oldURL := azureTDXIMDSQuoteURL
|
||||
defer func() { azureTDXIMDSQuoteURL = oldURL }()
|
||||
|
||||
tdReport := bytes.Repeat([]byte{0xA5}, azureTDReportSize)
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
||||
|
||||
var req tdxIMDSQuoteRequest
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
|
||||
assert.Equal(t, base64.RawURLEncoding.EncodeToString(tdReport), req.Report)
|
||||
|
||||
resp := tdxIMDSQuoteResponse{
|
||||
Quote: base64.RawURLEncoding.EncodeToString([]byte("quote")),
|
||||
}
|
||||
require.NoError(t, json.NewEncoder(w).Encode(resp))
|
||||
}))
|
||||
defer server.Close()
|
||||
azureTDXIMDSQuoteURL = server.URL
|
||||
|
||||
quote, err := (&defaultAzureTDXIMDSClient{}).GetQuote(context.Background(), tdReport, server.Client())
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []byte("quote"), quote)
|
||||
}
|
||||
|
||||
func TestIsAzureTDX_UsesHCLReportType(t *testing.T) {
|
||||
oldReader := azureTDXHCLReportReader
|
||||
defer func() { azureTDXHCLReportReader = oldReader }()
|
||||
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return testAzureHCLReport(azureHCLReportTypeSNP, []byte("runtime")), nil
|
||||
}
|
||||
assert.False(t, isAzureTDX())
|
||||
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return testAzureHCLReport(azureHCLReportTypeTDX, []byte(`{"keys":[]}`)), nil
|
||||
}
|
||||
assert.True(t, isAzureTDX())
|
||||
|
||||
azureTDXHCLReportReader = func() ([]byte, error) {
|
||||
return nil, fmt.Errorf("no vTPM")
|
||||
}
|
||||
assert.False(t, isAzureTDX())
|
||||
}
|
||||
|
||||
func TestExtractAzureTDXMeasurement_Success(t *testing.T) {
|
||||
oldValidator := DefaultValidator
|
||||
defer func() { DefaultValidator = oldValidator }()
|
||||
|
||||
DefaultValidator = &mockTokenValidator{
|
||||
validateFunc: func(token string) (map[string]any, error) {
|
||||
return map[string]any{
|
||||
"tdx_mrtd": "mrtd",
|
||||
"tdx_mrseam": "mrseam",
|
||||
"tdx_rtmr0": "rtmr0",
|
||||
"tdx_rtmr1": "rtmr1",
|
||||
"tdx_rtmr2": "rtmr2",
|
||||
"tdx_rtmr3": "rtmr3",
|
||||
"tdx_seamsvn": float64(7),
|
||||
"unrelatedKey": "ignored",
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
data, err := ExtractAzureTDXMeasurement("valid-token")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &AzureTDXMeasurementData{
|
||||
MRTD: "mrtd",
|
||||
MRSEAM: "mrseam",
|
||||
RTMRs: []string{"rtmr0", "rtmr1", "rtmr2", "rtmr3"},
|
||||
SEAMSVN: 7,
|
||||
}, data)
|
||||
}
|
||||
|
||||
func TestVerifier_VerifyWithCoRIM_AzureTDX(t *testing.T) {
|
||||
decodedQuote, err := tdxabi.QuoteToProto(tdxtestdata.RawQuote)
|
||||
require.NoError(t, err)
|
||||
|
||||
quoteV4, ok := decodedQuote.(*tdxpb.QuoteV4)
|
||||
require.True(t, ok)
|
||||
mrtd := quoteV4.GetTdQuoteBody().GetMrTd()
|
||||
|
||||
c := comid.NewComid()
|
||||
c.SetTagIdentity("tdx-tag", 0)
|
||||
|
||||
m := comid.MustNewUintMeasurement(uint64(1))
|
||||
m.AddDigest(swid.Sha384, mrtd)
|
||||
m.SetRawValueBytes([]byte("raw"), nil)
|
||||
|
||||
rv := comid.ReferenceValue{
|
||||
Environment: comid.Environment{
|
||||
Class: comid.NewClassOID("1.2.3.4"),
|
||||
},
|
||||
Measurements: comid.Measurements{*m},
|
||||
}
|
||||
c.AddReferenceValue(rv)
|
||||
|
||||
manifest := corim.NewUnsignedCorim()
|
||||
manifest.SetID("test-tdx-corim")
|
||||
manifest.AddComid(*c)
|
||||
|
||||
err = NewVerifier(&bytes.Buffer{}).VerifyWithCoRIM(tdxtestdata.RawQuote, manifest)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -5,16 +5,11 @@ package eat
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
sevsnppb "github.com/google/go-sev-guest/proto/sevsnp"
|
||||
tdxabi "github.com/google/go-tdx-guest/abi"
|
||||
tdxpb "github.com/google/go-tdx-guest/proto/tdx"
|
||||
attestpb "github.com/google/go-tpm-tools/proto/attest"
|
||||
tpmpb "github.com/google/go-tpm-tools/proto/tpm"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// OEMID constants (Private Enterprise Numbers).
|
||||
@@ -25,45 +20,18 @@ const (
|
||||
)
|
||||
|
||||
// extractSNPClaims extracts AMD SEV-SNP specific claims from binary report.
|
||||
// report may be one of three formats:
|
||||
// 1. proto-marshaled sevsnp.Attestation (SNP-only platform, from fetchSEVAttestation)
|
||||
// 2. proto-marshaled attest.Attestation (go-tpm-tools, SNP_VTPM platform, field 7 = SevSnpAttestation)
|
||||
// 3. raw binary SNP report (0x4A0 bytes)
|
||||
func extractSNPClaims(claims *EATClaims, report []byte) error {
|
||||
// Try sevsnp.Attestation (SNP-only proto format).
|
||||
var sevAttest sevsnppb.Attestation
|
||||
if err := proto.Unmarshal(report, &sevAttest); err == nil {
|
||||
if r := sevAttest.GetReport(); r != nil {
|
||||
return populateSNPClaims(claims, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Try attest.Attestation (go-tpm-tools SNP_VTPM format).
|
||||
var tpmAttest attestpb.Attestation
|
||||
if err := proto.Unmarshal(report, &tpmAttest); err == nil {
|
||||
if snp := tpmAttest.GetSevSnpAttestation(); snp != nil {
|
||||
if r := snp.GetReport(); r != nil {
|
||||
if err := populateSNPClaims(claims, r); err != nil {
|
||||
return err
|
||||
}
|
||||
populateVTPMClaims(claims, tpmAttest.GetQuotes(), tpmAttest.GetEventLog())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to raw binary SNP report.
|
||||
if len(report) < int(abi.ReportSize) {
|
||||
return fmt.Errorf("SNP report too small: got %d bytes, want at least %d", len(report), abi.ReportSize)
|
||||
}
|
||||
|
||||
// Parse SNP report structure
|
||||
snpReport, err := abi.ReportToProto(report[:abi.ReportSize])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse SNP report: %w", err)
|
||||
}
|
||||
return populateSNPClaims(claims, snpReport)
|
||||
}
|
||||
|
||||
func populateSNPClaims(claims *EATClaims, snpReport *sevsnppb.Report) error {
|
||||
// Extract SNP-specific fields
|
||||
claims.SNPExtensions = &SNPExtensions{
|
||||
Measurement: snpReport.Measurement,
|
||||
Policy: snpReport.Policy,
|
||||
@@ -74,16 +42,23 @@ func populateSNPClaims(claims *EATClaims, snpReport *sevsnppb.Report) error {
|
||||
PlatformInfo: snpReport.PlatformInfo,
|
||||
ChipID: snpReport.ChipId,
|
||||
}
|
||||
|
||||
// Set TCB version info
|
||||
claims.SNPExtensions.CurrentTCB = snpReport.CurrentTcb
|
||||
claims.SNPExtensions.ReportedTCB = snpReport.ReportedTcb
|
||||
claims.SNPExtensions.CommittedTCB = snpReport.CommittedTcb
|
||||
claims.SNPExtensions.LaunchTCB = snpReport.LaunchTcb
|
||||
claims.SNPExtensions.TCB = fmt.Sprintf("current:%d,reported:%d", snpReport.CurrentTcb, snpReport.ReportedTcb)
|
||||
claims.SNPExtensions.Signature = snpReport.Signature
|
||||
|
||||
// Set core EAT claims from SNP report
|
||||
claims.Measurements = snpReport.Measurement
|
||||
claims.UEID = snpReport.ChipId
|
||||
claims.OEMID = OEMID_AMD
|
||||
claims.UEID = snpReport.ChipId // Use ChipID as UEID
|
||||
claims.OEMID = OEMID_AMD // AMD's PEN (Private Enterprise Number)
|
||||
claims.SNPExtensions.Signature = snpReport.Signature
|
||||
|
||||
// Set hardware model (hash of product name)
|
||||
claims.HWModel = []byte(fmt.Sprintf("SEV-SNP-%d", snpReport.Version))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -95,59 +70,15 @@ func extractTDXClaims(claims *EATClaims, report []byte) error {
|
||||
return fmt.Errorf("failed to parse TDX quote: %w", err)
|
||||
}
|
||||
|
||||
var rtmrs [][]byte
|
||||
var mrTd []byte
|
||||
var xfam []byte
|
||||
var tdAttributes []byte
|
||||
var mrConfigId []byte
|
||||
var mrOwner []byte
|
||||
var mrOwnerConfig []byte
|
||||
var mrSeam []byte
|
||||
var signature []byte
|
||||
|
||||
switch q := decodedQuote.(type) {
|
||||
case *tdxpb.QuoteV4:
|
||||
tdReport := q.GetTdQuoteBody()
|
||||
signedData := q.GetSignedData()
|
||||
if tdReport == nil {
|
||||
return fmt.Errorf("missing TDX quote body")
|
||||
}
|
||||
rtmrs = tdReport.GetRtmrs()
|
||||
mrTd = tdReport.GetMrTd()
|
||||
xfam = tdReport.GetXfam()
|
||||
tdAttributes = tdReport.GetTdAttributes()
|
||||
mrConfigId = tdReport.GetMrConfigId()
|
||||
mrOwner = tdReport.GetMrOwner()
|
||||
mrOwnerConfig = tdReport.GetMrOwnerConfig()
|
||||
mrSeam = tdReport.GetMrSeam()
|
||||
if signedData != nil {
|
||||
signature = signedData.GetSignature()
|
||||
}
|
||||
case *tdxpb.QuoteV5:
|
||||
bodyDesc := q.GetTdQuoteBodyDescriptor()
|
||||
if bodyDesc == nil {
|
||||
return fmt.Errorf("missing TDX quote body descriptor")
|
||||
}
|
||||
tdReport := bodyDesc.GetTdQuoteBodyV5()
|
||||
if tdReport == nil {
|
||||
return fmt.Errorf("missing TDX quote body V5")
|
||||
}
|
||||
rtmrs = tdReport.GetRtmrs()
|
||||
mrTd = tdReport.GetMrTd()
|
||||
xfam = tdReport.GetXfam()
|
||||
tdAttributes = tdReport.GetTdAttributes()
|
||||
mrConfigId = tdReport.GetMrConfigId()
|
||||
mrOwner = tdReport.GetMrOwner()
|
||||
mrOwnerConfig = tdReport.GetMrOwnerConfig()
|
||||
mrSeam = tdReport.GetMrSeam()
|
||||
signedData := q.GetSignedData()
|
||||
if signedData != nil {
|
||||
signature = signedData.GetSignature()
|
||||
}
|
||||
default:
|
||||
quoteV4, ok := decodedQuote.(*tdxpb.QuoteV4)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported TDX quote format")
|
||||
}
|
||||
|
||||
tdReport := quoteV4.GetTdQuoteBody()
|
||||
signedData := quoteV4.GetSignedData()
|
||||
|
||||
rtmrs := tdReport.GetRtmrs()
|
||||
var rtmr0, rtmr1, rtmr2, rtmr3 []byte
|
||||
if len(rtmrs) > 0 {
|
||||
rtmr0 = rtmrs[0]
|
||||
@@ -163,22 +94,22 @@ func extractTDXClaims(claims *EATClaims, report []byte) error {
|
||||
}
|
||||
|
||||
claims.TDXExtensions = &TDXExtensions{
|
||||
MRTD: mrTd,
|
||||
MRTD: tdReport.GetMrTd(),
|
||||
RTMR0: rtmr0,
|
||||
RTMR1: rtmr1,
|
||||
RTMR2: rtmr2,
|
||||
RTMR3: rtmr3,
|
||||
XFAM: binary.LittleEndian.Uint64(xfam),
|
||||
TDAttributes: binary.LittleEndian.Uint64(tdAttributes),
|
||||
MRConfigID: mrConfigId,
|
||||
MROwner: mrOwner,
|
||||
MROwnerConfig: mrOwnerConfig,
|
||||
MRSEAM: mrSeam,
|
||||
Signature: signature,
|
||||
XFAM: binary.LittleEndian.Uint64(tdReport.GetXfam()),
|
||||
TDAttributes: binary.LittleEndian.Uint64(tdReport.GetTdAttributes()),
|
||||
MRConfigID: tdReport.GetMrConfigId(),
|
||||
MROwner: tdReport.GetMrOwner(),
|
||||
MROwnerConfig: tdReport.GetMrOwnerConfig(),
|
||||
MRSEAM: tdReport.GetMrSeam(),
|
||||
Signature: signedData.GetSignature(),
|
||||
}
|
||||
|
||||
// Set core EAT claims
|
||||
claims.Measurements = mrTd
|
||||
claims.Measurements = tdReport.GetMrTd()
|
||||
// Use first 32 bytes of MRTD as UEID, similar to other extractors
|
||||
if len(claims.Measurements) >= 32 {
|
||||
claims.UEID = claims.Measurements[:32]
|
||||
@@ -191,77 +122,18 @@ func extractTDXClaims(claims *EATClaims, report []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateVTPMClaims fills VTPMExtensions from go-tpm-tools quote banks and event log.
|
||||
// For SNP_VTPM the SNP measurement is already set; this adds PCR values alongside it.
|
||||
// PCR keys are formatted as "<hash>:<index>" (e.g. "sha256:0"), values are hex-encoded.
|
||||
// The raw TPMS_ATTEST bytes from the SHA-256 bank are stored as the canonical Quote.
|
||||
func populateVTPMClaims(claims *EATClaims, quotes []*tpmpb.Quote, eventLog []byte) {
|
||||
vtpm := &VTPMExtensions{
|
||||
PCRs: make(map[string]string),
|
||||
EventLog: eventLog,
|
||||
}
|
||||
|
||||
for _, q := range quotes {
|
||||
if q == nil {
|
||||
continue
|
||||
}
|
||||
pcrs := q.GetPcrs()
|
||||
if pcrs == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Prefer the SHA-256 bank as the canonical raw quote; fall back to the first available.
|
||||
if pcrs.GetHash() == tpmpb.HashAlgo_SHA256 || vtpm.Quote == nil {
|
||||
if raw := q.GetQuote(); len(raw) > 0 {
|
||||
vtpm.Quote = raw
|
||||
}
|
||||
}
|
||||
|
||||
hashName := tpmHashName(pcrs.GetHash())
|
||||
for idx, val := range pcrs.GetPcrs() {
|
||||
vtpm.PCRs[fmt.Sprintf("%s:%d", hashName, idx)] = hex.EncodeToString(val)
|
||||
}
|
||||
}
|
||||
|
||||
claims.VTPMExtensions = vtpm
|
||||
}
|
||||
|
||||
func tpmHashName(h tpmpb.HashAlgo) string {
|
||||
switch h {
|
||||
case tpmpb.HashAlgo_SHA1:
|
||||
return "sha1"
|
||||
case tpmpb.HashAlgo_SHA256:
|
||||
return "sha256"
|
||||
case tpmpb.HashAlgo_SHA384:
|
||||
return "sha384"
|
||||
case tpmpb.HashAlgo_SHA512:
|
||||
return "sha512"
|
||||
default:
|
||||
return fmt.Sprintf("hash%d", int(h))
|
||||
}
|
||||
}
|
||||
|
||||
// extractVTPMClaims extracts vTPM specific claims from a proto-marshaled attest.Attestation.
|
||||
// extractVTPMClaims extracts vTPM specific claims from binary report.
|
||||
func extractVTPMClaims(claims *EATClaims, report []byte) error {
|
||||
var tpmAttest attestpb.Attestation
|
||||
if err := proto.Unmarshal(report, &tpmAttest); err != nil {
|
||||
return fmt.Errorf("failed to parse vTPM attestation: %w", err)
|
||||
// vTPM report is typically a marshaled structure containing PCRs and quote
|
||||
// For now, store the entire report as the quote
|
||||
claims.VTPMExtensions = &VTPMExtensions{
|
||||
Quote: report,
|
||||
PCRs: make(map[string]string),
|
||||
}
|
||||
|
||||
populateVTPMClaims(claims, tpmAttest.GetQuotes(), tpmAttest.GetEventLog())
|
||||
|
||||
// Use PCR0 (SHA-256) as the canonical measurement if present.
|
||||
if ext := claims.VTPMExtensions; ext != nil {
|
||||
if v, ok := ext.PCRs["sha256:0"]; ok {
|
||||
b, err := hex.DecodeString(v)
|
||||
if err == nil {
|
||||
claims.Measurements = b
|
||||
if len(b) >= 16 {
|
||||
claims.UEID = b[:16]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set core EAT claims
|
||||
claims.Measurements = report[:32] // Use first 32 bytes as measurement
|
||||
claims.UEID = report[:16] // Use first 16 bytes as UEID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,11 +9,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-sev-guest/abi"
|
||||
attestpb "github.com/google/go-tpm-tools/proto/attest"
|
||||
tpmpb "github.com/google/go-tpm-tools/proto/tpm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestExtractSNPClaims(t *testing.T) {
|
||||
@@ -102,29 +99,16 @@ func TestTDXExtensionsJSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestExtractVTPMClaims(t *testing.T) {
|
||||
pcr0 := []byte("0123456789abcdef0123456789abcdef")
|
||||
rawQuote := []byte("raw-vtpm-quote")
|
||||
report, err := proto.Marshal(&attestpb.Attestation{
|
||||
Quotes: []*tpmpb.Quote{{
|
||||
Quote: rawQuote,
|
||||
Pcrs: &tpmpb.PCRs{
|
||||
Hash: tpmpb.HashAlgo_SHA256,
|
||||
Pcrs: map[uint32][]byte{
|
||||
0: pcr0,
|
||||
},
|
||||
},
|
||||
}},
|
||||
EventLog: []byte("event-log"),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
report := make([]byte, 32)
|
||||
copy(report, []byte("vtpm-report-with-enough-length-123"))
|
||||
|
||||
claims := &EATClaims{}
|
||||
err = extractVTPMClaims(claims, report)
|
||||
err := extractVTPMClaims(claims, report)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, claims.VTPMExtensions)
|
||||
assert.Equal(t, rawQuote, claims.VTPMExtensions.Quote)
|
||||
assert.Equal(t, pcr0, claims.Measurements)
|
||||
assert.Equal(t, pcr0[:16], claims.UEID)
|
||||
assert.Equal(t, report, claims.VTPMExtensions.Quote)
|
||||
assert.Equal(t, report[:32], claims.Measurements)
|
||||
assert.Equal(t, report[:16], claims.UEID)
|
||||
}
|
||||
|
||||
func TestExtractAzureClaims(t *testing.T) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/google/go-tdx-guest/abi"
|
||||
"github.com/google/go-tdx-guest/client"
|
||||
"github.com/google/go-tdx-guest/proto/checkconfig"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-tpm-tools/client"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
|
||||
@@ -5,7 +5,7 @@ package agent
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
|
||||
@@ -55,8 +55,6 @@ func (c *client) GetAttestation(ctx context.Context, reportData [64]byte, nonce
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_VTPM
|
||||
case attestation.SNPvTPM:
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_SNP_VTPM
|
||||
case attestation.Azure:
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_AZURE
|
||||
default:
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_UNSPECIFIED
|
||||
}
|
||||
@@ -94,8 +92,6 @@ func (c *client) GetRawEvidence(ctx context.Context, reportData [64]byte, nonce
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_VTPM
|
||||
case attestation.SNPvTPM:
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_SNP_VTPM
|
||||
case attestation.Azure:
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_AZURE
|
||||
default:
|
||||
platformType = attestation_v1.PlatformType_PLATFORM_TYPE_UNSPECIFIED
|
||||
}
|
||||
|
||||
@@ -174,40 +174,6 @@ func TestGetAttestationTDX(t *testing.T) {
|
||||
assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_TDX, mockServer.lastPlatformType)
|
||||
}
|
||||
|
||||
// TestGetAttestationAzure tests getting Azure attestation.
|
||||
func TestGetAttestationAzure(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
socketPath := filepath.Join(tmpDir, "attestation-azure-evidence.sock")
|
||||
|
||||
listener, err := net.Listen("unix", socketPath)
|
||||
require.NoError(t, err)
|
||||
defer listener.Close()
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
mockServer := &mockAttestationServer{}
|
||||
attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer)
|
||||
|
||||
go func() {
|
||||
_ = grpcServer.Serve(listener)
|
||||
}()
|
||||
defer grpcServer.Stop()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
client, err := NewClient(socketPath)
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
var reportData [64]byte
|
||||
var nonce [32]byte
|
||||
|
||||
quote, err := client.GetAttestation(ctx, reportData, nonce, attestation.Azure)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, quote)
|
||||
assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_AZURE, mockServer.lastPlatformType)
|
||||
}
|
||||
|
||||
// TestGetAttestationVTPM tests getting vTPM attestation.
|
||||
func TestGetAttestationVTPM(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
@@ -513,40 +479,6 @@ func TestGetRawEvidenceTDX(t *testing.T) {
|
||||
assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_TDX, mockServer.lastPlatformType)
|
||||
}
|
||||
|
||||
// TestGetRawEvidenceAzure tests getting raw evidence for Azure platform.
|
||||
func TestGetRawEvidenceAzure(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
socketPath := filepath.Join(tmpDir, "raw-evidence-azure.sock")
|
||||
|
||||
listener, err := net.Listen("unix", socketPath)
|
||||
require.NoError(t, err)
|
||||
defer listener.Close()
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
mockServer := &mockAttestationServer{}
|
||||
attestation_v1.RegisterAttestationServiceServer(grpcServer, mockServer)
|
||||
|
||||
go func() {
|
||||
_ = grpcServer.Serve(listener)
|
||||
}()
|
||||
defer grpcServer.Stop()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
client, err := NewClient(socketPath)
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
var reportData [64]byte
|
||||
var nonce [32]byte
|
||||
|
||||
evidence, err := client.GetRawEvidence(ctx, reportData, nonce, attestation.Azure)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, evidence)
|
||||
assert.Equal(t, attestation_v1.PlatformType_PLATFORM_TYPE_AZURE, mockServer.lastPlatformType)
|
||||
}
|
||||
|
||||
// TestGetRawEvidenceVTPM tests getting raw evidence for VTPM platform.
|
||||
func TestGetRawEvidenceVTPM(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/ultravioletrs/cocos/pkg/atls"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients"
|
||||
"github.com/ultravioletrs/cocos/pkg/tls"
|
||||
|
||||
@@ -5,7 +5,7 @@ package manager
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients"
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user