mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
NOISSUE - Add path to expected PCR values (#398)
* add path to expected PCR values * change rust scrtip for attestation policy to print policy to stdout * fix cli test * remove stdout from cmd config struct * fix manager test * fix manager readme
This commit is contained in:
committed by
GitHub
parent
33744a12a8
commit
293c65a3aa
+17
-1
@@ -639,7 +639,23 @@ func (cli *CLI) NewMeasureCmd(igvmBinaryPath string) *cobra.Command {
|
||||
|
||||
inputFile := args[0]
|
||||
|
||||
return cli.measurement.Run(inputFile)
|
||||
measurement, err := cli.measurement.Run(inputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputString := string(measurement)
|
||||
lines := strings.Split(strings.TrimSpace(outputString), "\n")
|
||||
|
||||
if len(lines) == 1 {
|
||||
outputString = strings.ToLower(outputString)
|
||||
} else {
|
||||
return fmt.Errorf("error: %s", outputString)
|
||||
}
|
||||
|
||||
cmd.Print(outputString)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
@@ -109,27 +110,31 @@ func changeAttestationConfiguration(fileName, base64Data string, expectedLength
|
||||
return errDataLength
|
||||
}
|
||||
|
||||
ac := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
ac := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
attestationPolicy, err := os.ReadFile(fileName)
|
||||
f, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return errors.Wrap(errReadingAttestationPolicyFile, err)
|
||||
}
|
||||
|
||||
if err = protojson.Unmarshal(attestationPolicy, &ac); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(f, &ac); err != nil {
|
||||
return errors.Wrap(errUnmarshalJSON, err)
|
||||
}
|
||||
|
||||
if ac.Config.Policy == nil {
|
||||
ac.Config.Policy = &check.Policy{}
|
||||
}
|
||||
|
||||
switch field {
|
||||
case measurementField:
|
||||
ac.Policy.Measurement = data
|
||||
ac.Config.Policy.Measurement = data
|
||||
case hostDataField:
|
||||
ac.Policy.HostData = data
|
||||
ac.Config.Policy.HostData = data
|
||||
default:
|
||||
return errAttestationPolicyField
|
||||
}
|
||||
|
||||
fileJson, err := protojson.Marshal(&ac)
|
||||
fileJson, err := json.MarshalIndent(&ac, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(errMarshalJSON, err)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
@@ -18,9 +19,9 @@ func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
initialConfig := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
initialConfig := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
initialJSON, err := protojson.Marshal(&initialConfig)
|
||||
initialJSON, err := json.Marshal(initialConfig)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(tmpfile.Name(), initialJSON, 0o644)
|
||||
require.NoError(t, err)
|
||||
@@ -86,15 +87,15 @@ func TestChangeAttestationConfiguration(t *testing.T) {
|
||||
content, err := os.ReadFile(tmpfile.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
config := check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}
|
||||
err = protojson.Unmarshal(content, &config)
|
||||
ap := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err = config.ReadAttestationPolicyFromByte(content, &ap)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedData, _ := base64.StdEncoding.DecodeString(tt.base64Data)
|
||||
if tt.field == measurementField {
|
||||
assert.Equal(t, decodedData, config.Policy.Measurement)
|
||||
assert.Equal(t, decodedData, ap.Config.Policy.Measurement)
|
||||
} else if tt.field == hostDataField {
|
||||
assert.Equal(t, decodedData, config.Policy.HostData)
|
||||
assert.Equal(t, decodedData, ap.Config.Policy.HostData)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -287,9 +287,9 @@ type MockMeasurement struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockMeasurement) Run(igvmBinaryPath string) error {
|
||||
func (m *MockMeasurement) Run(igvmBinaryPath string) ([]byte, error) {
|
||||
args := m.Called(igvmBinaryPath)
|
||||
return args.Error(0)
|
||||
return nil, args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockMeasurement) Stop() error {
|
||||
|
||||
+2
-2
@@ -27,14 +27,14 @@ func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
|
||||
Example: "ca-bundle <path_to_platform_info_json>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
attestationConfiguration := config.Config{SnpCheck: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
attestationConfiguration := config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
err := config.ReadAttestationPolicy(args[0], &attestationConfiguration)
|
||||
if err != nil {
|
||||
printError(cmd, "Error while reading manifest: %v ❌ ", err)
|
||||
return
|
||||
}
|
||||
|
||||
product := attestationConfiguration.SnpCheck.RootOfTrust.ProductLine
|
||||
product := attestationConfiguration.Config.RootOfTrust.ProductLine
|
||||
|
||||
getter := trust.DefaultHTTPSGetter()
|
||||
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/manager"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/igvmmeasure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc/agent"
|
||||
managergrpc "github.com/ultravioletrs/cocos/pkg/clients/grpc/manager"
|
||||
@@ -23,10 +23,10 @@ type CLI struct {
|
||||
client grpc.Client
|
||||
managerClient manager.ManagerServiceClient
|
||||
connectErr error
|
||||
measurement igvmmeasure.MeasurementProvider
|
||||
measurement cmdconfig.MeasurementProvider
|
||||
}
|
||||
|
||||
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig, measurement igvmmeasure.MeasurementProvider) *CLI {
|
||||
func New(agentConfig grpc.AgentClientConfig, managerConfig grpc.ManagerClientConfig, measurement cmdconfig.MeasurementProvider) *CLI {
|
||||
return &CLI{
|
||||
agentConfig: agentConfig,
|
||||
managerConfig: managerConfig,
|
||||
|
||||
+3
-2
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/ultravioletrs/cocos/cli"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/igvmmeasure"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
cmd "github.com/virtee/sev-snp-measure-go/sevsnpmeasure/cmd"
|
||||
)
|
||||
@@ -108,7 +108,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
measurement, err := igvmmeasure.NewIgvmMeasurement(cfg.IgvmBinaryPath, os.Stderr, os.Stdout)
|
||||
options := cmdconfig.IgvmMeasureOptions
|
||||
measurement, err := cmdconfig.NewCmdConfig(cfg.IgvmBinaryPath, options, os.Stderr)
|
||||
if err != nil {
|
||||
message := color.New(color.FgRed).Sprintf("failed to initialize measurement: %s", err) // Use %s instead of %w
|
||||
rootCmd.Println(message)
|
||||
|
||||
+6
-4
@@ -42,7 +42,9 @@ type config struct {
|
||||
JaegerURL url.URL `env:"COCOS_JAEGER_URL" envDefault:"http://localhost:4318"`
|
||||
TraceRatio float64 `env:"COCOS_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
InstanceID string `env:"MANAGER_INSTANCE_ID" envDefault:""`
|
||||
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build"`
|
||||
AttestationPolicyBinary string `env:"MANAGER_ATTESTATION_POLICY_BINARY" envDefault:"../../build/attestation_policy"`
|
||||
IgvmMeasureBinary string `env:"MANAGER_IGVMMEASURE_BINARY" envDefault:"../../build/igvmmeasure"`
|
||||
PcrValues string `env:"MANAGER_PCR_VALUES" envDefault:""`
|
||||
EosVersion string `env:"MANAGER_EOS_VERSION" envDefault:""`
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, cfg.EosVersion)
|
||||
svc, err := newService(logger, tracer, qemuCfg, cfg.AttestationPolicyBinary, cfg.IgvmMeasureBinary, cfg.PcrValues, cfg.EosVersion)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
@@ -125,8 +127,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, eosVersion string) (manager.Service, error) {
|
||||
svc, err := manager.New(qemuCfg, attestationPolicyPath, logger, qemu.NewVM, eosVersion)
|
||||
func newService(logger *slog.Logger, tracer trace.Tracer, qemuCfg qemu.Config, attestationPolicyPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, eosVersion string) (manager.Service, error) {
|
||||
svc, err := manager.New(qemuCfg, attestationPolicyPath, igvmMeasurementBinaryPath, pcrValuesFilePath, logger, qemu.NewVM, eosVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ COCOS_JAEGER_TRACE_RATIO=1.0
|
||||
# Manager Service Configuration
|
||||
MANAGER_INSTANCE_ID=
|
||||
MANAGER_ATTESTATION_POLICY_BINARY=../../build
|
||||
MANAGER_IGVMMEASURE_BINARY=../../build
|
||||
MANAGER_PCR_VALUES=/etc/cocos/pcr_values.json
|
||||
MANAGER_GRPC_CLIENT_CERT=
|
||||
MANAGER_GRPC_CLIENT_KEY=
|
||||
MANAGER_GRPC_SERVER_CA_CERTS=
|
||||
|
||||
+3
-1
@@ -11,7 +11,9 @@ The service is configured using the environment variables from the following tab
|
||||
| COCOS_JAEGER_URL | The URL for the Jaeger tracing endpoint. | http://localhost:4318 |
|
||||
| COCOS_JAEGER_TRACE_RATIO | The ratio of traces to sample. | 1.0 |
|
||||
| MANAGER_INSTANCE_ID | The instance ID for the manager service. | |
|
||||
| MANAGER_ATTESTATION_POLICY_BINARY | The file path for the attestation policy and igvmmeassure binaries. | ../../build |
|
||||
| MANAGER_ATTESTATION_POLICY_BINARY | The file path for the attestation policy binarie. | ../../build |
|
||||
| MANAGER_IGVMMEASURE_BINARY | The file path for the igvmmeasure binarie. | ../../build |
|
||||
| MANAGER_PCR_VALUES | The file path for the file with the expected PCR values. | |
|
||||
| MANAGER_GRPC_CLIENT_CERT | The file path for the client certificate. | |
|
||||
| MANAGER_GRPC_CLIENT_KEY | The file path for the client private key. | |
|
||||
| MANAGER_GRPC_SERVER_CA_CERTS | The file path for the server CA certificate(s). | |
|
||||
|
||||
@@ -11,24 +11,38 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/igvmmeasure"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/virtee/sev-snp-measure-go/cpuid"
|
||||
"github.com/virtee/sev-snp-measure-go/guest"
|
||||
"github.com/virtee/sev-snp-measure-go/vmmtypes"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
const defGuestFeatures = 0x1
|
||||
|
||||
func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationId string) ([]byte, error) {
|
||||
cmd := exec.Command("sudo", fmt.Sprintf("%s/attestation_policy", ms.attestationPolicyBinaryPath), "--policy", "196608")
|
||||
var stderrBuffer bytes.Buffer
|
||||
options := []string{"--policy", "196608"}
|
||||
|
||||
if ms.pcrValuesFilePath != "" {
|
||||
pcrValues := []string{"--pcr", ms.pcrValuesFilePath}
|
||||
options = append(options, pcrValues...)
|
||||
}
|
||||
|
||||
stderr := bufio.NewWriter(&stderrBuffer)
|
||||
|
||||
attestPolicyCmd, err := cmdconfig.NewCmdConfig("sudo", options, stderr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ms.mu.Lock()
|
||||
vm, exists := ms.vms[computationId]
|
||||
@@ -43,22 +57,15 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
_, err := cmd.Output()
|
||||
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
f, err := os.ReadFile("./attestation_policy.json")
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
var attestationPolicy check.Config
|
||||
|
||||
if err = protojson.Unmarshal(f, &attestationPolicy); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -70,29 +77,37 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
return nil, err
|
||||
}
|
||||
case vmi.Config.EnableSEVSNP:
|
||||
igvmMeasurementBinaryPath := fmt.Sprintf("%s/igvmmeasure", ms.attestationPolicyBinaryPath)
|
||||
|
||||
var stdoutBuffer bytes.Buffer
|
||||
var stderrBuffer bytes.Buffer
|
||||
|
||||
stdout := bufio.NewWriter(&stdoutBuffer)
|
||||
stderr := bufio.NewWriter(&stderrBuffer)
|
||||
options := cmdconfig.IgvmMeasureOptions
|
||||
|
||||
igvmMeasurement, err := igvmmeasure.NewIgvmMeasurement(igvmMeasurementBinaryPath, stderr, stdout)
|
||||
igvmMeasurement, err := cmdconfig.NewCmdConfig(ms.igvmMeasurementBinaryPath, options, stderr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = igvmMeasurement.Run(ms.qemuCfg.IGVMConfig.File)
|
||||
outputByte, err := igvmMeasurement.Run(ms.qemuCfg.IGVMConfig.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
measurement = stdoutBuffer.Bytes()
|
||||
outputString := string(outputByte)
|
||||
lines := strings.Split(strings.TrimSpace(outputString), "\n")
|
||||
|
||||
if len(lines) == 1 {
|
||||
outputString = strings.TrimSpace(outputString)
|
||||
outputString = strings.ToLower(outputString)
|
||||
} else {
|
||||
return nil, fmt.Errorf("error: %s", outputString)
|
||||
}
|
||||
|
||||
measurement, err = hex.DecodeString(outputString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if measurement != nil {
|
||||
attestationPolicy.Policy.Measurement = measurement
|
||||
attestationPolicy.Config.Policy.Measurement = measurement
|
||||
}
|
||||
|
||||
if vmi.Config.SevConfig.EnableHostData {
|
||||
@@ -100,12 +115,12 @@ func (ms *managerService) FetchAttestationPolicy(_ context.Context, computationI
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attestationPolicy.Policy.HostData = hostData
|
||||
attestationPolicy.Config.Policy.HostData = hostData
|
||||
}
|
||||
|
||||
attestationPolicy.Policy.MinimumLaunchTcb = vmi.LaunchTCB
|
||||
attestationPolicy.Config.Policy.MinimumLaunchTcb = vmi.LaunchTCB
|
||||
|
||||
f, err = protojson.Marshal(&attestationPolicy)
|
||||
f, err := json.MarshalIndent(attestationPolicy, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@@ -20,7 +21,7 @@ func CreateDummyAttestationPolicyBinary(t *testing.T, behavior string) string {
|
||||
switch behavior {
|
||||
case "success":
|
||||
content = []byte(`#!/bin/sh
|
||||
echo '{"policy": {"measurement": null, "host_data": null}}' > attestation_policy.json
|
||||
echo '{"pcr_values": {"sha256": null, "sha384": null}, "policy": {"measurement": null, "host_data": null}}'
|
||||
`)
|
||||
case "fail":
|
||||
content = []byte(`#!/bin/sh
|
||||
@@ -105,7 +106,7 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
},
|
||||
LaunchTCB: 0,
|
||||
},
|
||||
expectedError: "no such file or directory",
|
||||
expectedError: "failed to decode Attestation Policy file",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -116,7 +117,8 @@ func TestFetchAttestationPolicy(t *testing.T) {
|
||||
|
||||
ms := &managerService{
|
||||
vms: make(map[string]vm.VM),
|
||||
attestationPolicyBinaryPath: tempDir,
|
||||
attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"),
|
||||
pcrValuesFilePath: tempDir,
|
||||
qemuCfg: qemu.Config{
|
||||
CPU: "EPYC",
|
||||
},
|
||||
|
||||
+26
-14
@@ -3,13 +3,14 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/manager/vm"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
"github.com/ultravioletrs/cocos/pkg/attestation/cmdconfig"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
@@ -85,6 +87,8 @@ type managerService struct {
|
||||
ap sync.Mutex
|
||||
qemuCfg qemu.Config
|
||||
attestationPolicyBinaryPath string
|
||||
igvmMeasurementBinaryPath string
|
||||
pcrValuesFilePath string
|
||||
logger *slog.Logger
|
||||
vms map[string]vm.VM
|
||||
vmFactory vm.Provider
|
||||
@@ -97,7 +101,7 @@ type managerService struct {
|
||||
var _ Service = (*managerService)(nil)
|
||||
|
||||
// New instantiates the manager service implementation.
|
||||
func New(cfg qemu.Config, attestationPolicyBinPath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
|
||||
func New(cfg qemu.Config, attestationPolicyBinPath string, igvmMeasurementBinaryPath string, pcrValuesFilePath string, logger *slog.Logger, vmFactory vm.Provider, eosVersion string) (Service, error) {
|
||||
start, end, err := decodeRange(cfg.HostFwdRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -114,6 +118,8 @@ func New(cfg qemu.Config, attestationPolicyBinPath string, logger *slog.Logger,
|
||||
vms: make(map[string]vm.VM),
|
||||
vmFactory: vmFactory,
|
||||
attestationPolicyBinaryPath: attestationPolicyBinPath,
|
||||
igvmMeasurementBinaryPath: igvmMeasurementBinaryPath,
|
||||
pcrValuesFilePath: pcrValuesFilePath,
|
||||
portRangeMin: start,
|
||||
portRangeMax: end,
|
||||
persistence: persistence,
|
||||
@@ -150,30 +156,36 @@ func (ms *managerService) CreateVM(ctx context.Context, req *CreateReq) (string,
|
||||
cfg.Config.EnvMount = tmpEnvDir
|
||||
|
||||
if ms.qemuCfg.EnableSEVSNP || ms.qemuCfg.EnableSEV {
|
||||
cmd := exec.Command("sudo", fmt.Sprintf("%s/attestation_policy", ms.attestationPolicyBinaryPath), "--policy", "196608")
|
||||
var stderrBuffer bytes.Buffer
|
||||
options := []string{"--policy", "196608"}
|
||||
|
||||
if ms.pcrValuesFilePath != "" {
|
||||
pcrValues := []string{"--pcr", ms.pcrValuesFilePath}
|
||||
options = append(options, pcrValues...)
|
||||
}
|
||||
|
||||
stderr := bufio.NewWriter(&stderrBuffer)
|
||||
|
||||
attestPolicyCmd, err := cmdconfig.NewCmdConfig("sudo", options, stderr)
|
||||
if err != nil {
|
||||
return "", id, err
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
_, err := cmd.Output()
|
||||
stdOutByte, err := attestPolicyCmd.Run(ms.attestationPolicyBinaryPath)
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return "", id, errors.Wrap(ErrFailedToCreateAttestationPolicy, err)
|
||||
}
|
||||
|
||||
ms.ap.Lock()
|
||||
f, err := os.ReadFile("./attestation_policy.json")
|
||||
ms.ap.Unlock()
|
||||
if err != nil {
|
||||
return "", id, errors.Wrap(ErrFailedToReadPolicy, err)
|
||||
}
|
||||
attestationPolicy := config.Config{Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
attestationPolicy := config.Config{SnpCheck: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
if err = config.ReadAttestationPolicyFromByte(f, &attestationPolicy); err != nil {
|
||||
if err = config.ReadAttestationPolicyFromByte(stdOutByte, &attestationPolicy); err != nil {
|
||||
return "", id, errors.Wrap(ErrUnmarshalFailed, err)
|
||||
}
|
||||
|
||||
// Define the TCB that was present at launch of the VM.
|
||||
cfg.LaunchTCB = attestationPolicy.SnpCheck.Policy.MinimumLaunchTcb
|
||||
cfg.LaunchTCB = attestationPolicy.Config.Policy.MinimumLaunchTcb
|
||||
}
|
||||
|
||||
agentPort, err := getFreePort(ms.portRangeMin, ms.portRangeMax)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
mglog "github.com/absmach/magistrala/logger"
|
||||
@@ -29,7 +30,7 @@ func TestNew(t *testing.T) {
|
||||
logger := slog.Default()
|
||||
vmf := new(mocks.Provider)
|
||||
|
||||
service, err := New(cfg, "", logger, vmf.Execute, "")
|
||||
service, err := New(cfg, "", "", "", logger, vmf.Execute, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotNil(t, service)
|
||||
@@ -94,7 +95,8 @@ func TestRun(t *testing.T) {
|
||||
|
||||
ms := &managerService{
|
||||
qemuCfg: qemuCfg,
|
||||
attestationPolicyBinaryPath: tempDir,
|
||||
attestationPolicyBinaryPath: path.Join(tempDir, "attestation_policy"),
|
||||
pcrValuesFilePath: tempDir,
|
||||
logger: logger,
|
||||
vms: make(map[string]vm.VM),
|
||||
vmFactory: vmf.Execute,
|
||||
|
||||
+14
-29
@@ -1,48 +1,46 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package igvmmeasure
|
||||
package cmdconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var IgvmMeasureOptions = []string{"measure", "-b"}
|
||||
|
||||
type MeasurementProvider interface {
|
||||
Run(igvmBinaryPath string) error
|
||||
Run(binaryPath string) ([]byte, error)
|
||||
Stop() error
|
||||
}
|
||||
type IgvmMeasurement struct {
|
||||
type CmdConfig struct {
|
||||
binPath string
|
||||
options []string
|
||||
stderr io.Writer
|
||||
stdout io.Writer
|
||||
cmd *exec.Cmd
|
||||
execCommand func(name string, arg ...string) *exec.Cmd
|
||||
}
|
||||
|
||||
func NewIgvmMeasurement(binPath string, stderr, stdout io.Writer) (*IgvmMeasurement, error) {
|
||||
func NewCmdConfig(binPath string, options []string, stderr io.Writer) (*CmdConfig, error) {
|
||||
if binPath == "" {
|
||||
return nil, fmt.Errorf("pathToBinary cannot be empty")
|
||||
}
|
||||
|
||||
return &IgvmMeasurement{
|
||||
return &CmdConfig{
|
||||
binPath: binPath,
|
||||
options: options,
|
||||
stderr: stderr,
|
||||
stdout: stdout,
|
||||
execCommand: exec.Command,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *IgvmMeasurement) Run(pathToFile string) error {
|
||||
func (m *CmdConfig) Run(pathToFile string) ([]byte, error) {
|
||||
binary := m.binPath
|
||||
args := []string{}
|
||||
args = append(args, m.options...)
|
||||
args = append(args, pathToFile)
|
||||
args = append(args, "measure")
|
||||
args = append(args, "-b")
|
||||
args = append(args, m.options...)
|
||||
|
||||
outBuf := &bytes.Buffer{}
|
||||
cmd := m.execCommand(binary, args...)
|
||||
@@ -50,26 +48,13 @@ func (m *IgvmMeasurement) Run(pathToFile string) error {
|
||||
cmd.Stdout = outBuf
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
outputString := outBuf.String()
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(outputString), "\n")
|
||||
|
||||
if len(lines) == 1 {
|
||||
outputString = strings.ToLower(outputString)
|
||||
_, err := m.stdout.Write([]byte(outputString))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error: %s", outputString)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return outBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (m *IgvmMeasurement) Stop() error {
|
||||
func (m *CmdConfig) Stop() error {
|
||||
if m.cmd == nil || m.cmd.Process == nil {
|
||||
return fmt.Errorf("no running process to stop")
|
||||
}
|
||||
@@ -82,6 +67,6 @@ func (m *IgvmMeasurement) Stop() error {
|
||||
}
|
||||
|
||||
// SetExecCommand allows tests to inject a mock execCommand function.
|
||||
func (m *IgvmMeasurement) SetExecCommand(cmdFunc func(name string, arg ...string) *exec.Cmd) {
|
||||
func (m *CmdConfig) SetExecCommand(cmdFunc func(name string, arg ...string) *exec.Cmd) {
|
||||
m.execCommand = cmdFunc
|
||||
}
|
||||
+9
-10
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package igvmmeasure
|
||||
package cmdconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -14,15 +14,15 @@ import (
|
||||
func TestIgvmMeasurement(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func() *IgvmMeasurement
|
||||
setup func() *CmdConfig
|
||||
runArgs string
|
||||
expectErr bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "NewIgvmMeasurement - Empty pathToBinary",
|
||||
setup: func() *IgvmMeasurement {
|
||||
igvm, err := NewIgvmMeasurement("", nil, nil)
|
||||
setup: func() *CmdConfig {
|
||||
igvm, err := NewCmdConfig("", []string{""}, nil)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, igvm)
|
||||
return nil
|
||||
@@ -32,8 +32,8 @@ func TestIgvmMeasurement(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Run - Successful Execution",
|
||||
setup: func() *IgvmMeasurement {
|
||||
igvm, _ := NewIgvmMeasurement("/valid/path", nil, nil)
|
||||
setup: func() *CmdConfig {
|
||||
igvm, _ := NewCmdConfig("/valid/path", []string{""}, nil)
|
||||
igvm.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
|
||||
return exec.Command("sh", "-c", "echo 'measurement successful'")
|
||||
})
|
||||
@@ -43,8 +43,8 @@ func TestIgvmMeasurement(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Run - Failure Execution",
|
||||
setup: func() *IgvmMeasurement {
|
||||
igvm, _ := NewIgvmMeasurement("/invalid/path", nil, nil)
|
||||
setup: func() *CmdConfig {
|
||||
igvm, _ := NewCmdConfig("/invalid/path", []string{""}, nil)
|
||||
igvm.SetExecCommand(func(name string, arg ...string) *exec.Cmd {
|
||||
return exec.Command("sh", "-c", "echo 'some error occurred\nextra line' && exit 1")
|
||||
})
|
||||
@@ -61,10 +61,9 @@ func TestIgvmMeasurement(t *testing.T) {
|
||||
|
||||
if igvm != nil {
|
||||
buf := new(bytes.Buffer)
|
||||
igvm.stdout = buf
|
||||
igvm.stderr = buf
|
||||
|
||||
err := igvm.Run(tc.runArgs)
|
||||
_, err := igvm.Run(tc.runArgs)
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(tc.expectedErr), strings.TrimSpace(err.Error()))
|
||||
@@ -21,10 +21,11 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
AttestationPolicy = Config{SnpCheck: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &PcrConfig{}}
|
||||
AttestationPolicy = Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &PcrConfig{}}
|
||||
ErrAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
|
||||
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
|
||||
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
|
||||
ErrAttestationPolicyEncode = errors.New("failed to encode the Attestation Policy")
|
||||
)
|
||||
|
||||
type PcrValues struct {
|
||||
@@ -37,8 +38,8 @@ type PcrConfig struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SnpCheck *check.Config
|
||||
PcrConfig *PcrConfig
|
||||
*check.Config
|
||||
*PcrConfig
|
||||
}
|
||||
|
||||
func ReadAttestationPolicy(policyPath string, attestationConfiguration *Config) error {
|
||||
@@ -57,7 +58,7 @@ func ReadAttestationPolicy(policyPath string, attestationConfiguration *Config)
|
||||
func ReadAttestationPolicyFromByte(policyData []byte, attestationConfiguration *Config) error {
|
||||
unmarshalOptions := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
|
||||
|
||||
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.SnpCheck); err != nil {
|
||||
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.Config); err != nil {
|
||||
return errors.Wrap(ErrAttestationPolicyDecode, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ func GetLeveledQuoteProvider() (client.LeveledQuoteProvider, error) {
|
||||
}
|
||||
|
||||
func VerifyAttestationReportTLS(attestationPB *sevsnp.Attestation, reportData []byte) error {
|
||||
config, err := copyConfig(config.AttestationPolicy.SnpCheck)
|
||||
config, err := copyConfig(config.AttestationPolicy.Config)
|
||||
if err != nil {
|
||||
return errors.Wrap(fmt.Errorf("failed to create a copy of attestation policy"), err)
|
||||
}
|
||||
|
||||
@@ -145,8 +145,8 @@ func TestVerifyAttestationReportUnknownProduct(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config.AttestationPolicy.SnpCheck.RootOfTrust.ProductLine = ""
|
||||
config.AttestationPolicy.SnpCheck.Policy.Product = nil
|
||||
config.AttestationPolicy.Config.RootOfTrust.ProductLine = ""
|
||||
config.AttestationPolicy.Config.Policy.Product = nil
|
||||
err := VerifyAttestationReportTLS(tt.attestationReport, tt.reportData)
|
||||
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
|
||||
})
|
||||
@@ -192,23 +192,23 @@ func prepVerifyAttReport(t *testing.T) (*sevsnp.Attestation, []byte) {
|
||||
rr, err := abi.ReportCertsToProto(file)
|
||||
require.NoError(t, err)
|
||||
|
||||
config.AttestationPolicy = config.Config{SnpCheck: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
config.AttestationPolicy = config.Config{Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}}, PcrConfig: &config.PcrConfig{}}
|
||||
|
||||
attestationPolicyFile, err := os.ReadFile("../../../scripts/attestation_policy/attestation_policy.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
unmarshalOptions := protojson.UnmarshalOptions{DiscardUnknown: true}
|
||||
|
||||
err = unmarshalOptions.Unmarshal(attestationPolicyFile, config.AttestationPolicy.SnpCheck)
|
||||
err = unmarshalOptions.Unmarshal(attestationPolicyFile, config.AttestationPolicy.Config)
|
||||
require.NoError(t, err)
|
||||
|
||||
config.AttestationPolicy.SnpCheck.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
|
||||
config.AttestationPolicy.SnpCheck.Policy.FamilyId = rr.Report.FamilyId
|
||||
config.AttestationPolicy.SnpCheck.Policy.ImageId = rr.Report.ImageId
|
||||
config.AttestationPolicy.SnpCheck.Policy.Measurement = rr.Report.Measurement
|
||||
config.AttestationPolicy.SnpCheck.Policy.HostData = rr.Report.HostData
|
||||
config.AttestationPolicy.SnpCheck.Policy.ReportIdMa = rr.Report.ReportIdMa
|
||||
config.AttestationPolicy.SnpCheck.RootOfTrust.ProductLine = sevProductNameMilan
|
||||
config.AttestationPolicy.Config.Policy.Product = &sevsnp.SevProduct{Name: sevsnp.SevProduct_SEV_PRODUCT_MILAN}
|
||||
config.AttestationPolicy.Config.Policy.FamilyId = rr.Report.FamilyId
|
||||
config.AttestationPolicy.Config.Policy.ImageId = rr.Report.ImageId
|
||||
config.AttestationPolicy.Config.Policy.Measurement = rr.Report.Measurement
|
||||
config.AttestationPolicy.Config.Policy.HostData = rr.Report.HostData
|
||||
config.AttestationPolicy.Config.Policy.ReportIdMa = rr.Report.ReportIdMa
|
||||
config.AttestationPolicy.Config.RootOfTrust.ProductLine = sevProductNameMilan
|
||||
|
||||
return rr, rr.Report.ReportData
|
||||
}
|
||||
|
||||
@@ -251,13 +251,13 @@ func TestReadAttestationPolicy(t *testing.T) {
|
||||
defer os.Remove(tt.manifestPath)
|
||||
}
|
||||
|
||||
config := att.Config{SnpCheck: &check.Config{}, PcrConfig: &att.PcrConfig{}}
|
||||
config := att.Config{Config: &check.Config{}, PcrConfig: &att.PcrConfig{}}
|
||||
err := att.ReadAttestationPolicy(tt.manifestPath, &config)
|
||||
|
||||
assert.True(t, errors.Contains(err, tt.err), fmt.Sprintf("expected error %v, got %v", tt.err, err))
|
||||
if tt.err == nil {
|
||||
assert.NotNil(t, config.SnpCheck.Policy)
|
||||
assert.NotNil(t, config.SnpCheck.RootOfTrust)
|
||||
assert.NotNil(t, config.Config.Policy)
|
||||
assert.NotNil(t, config.Config.RootOfTrust)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ Then run the binary. Keep in mind that you have to specify the policy of the Gue
|
||||
cd ./target/release
|
||||
|
||||
# Run with option --policy (policy is 64 bit number)
|
||||
./attestation_policy --policy 196608
|
||||
./attestation_policy --policy 196608 --pcr ../../pcr_values.json
|
||||
```
|
||||
@@ -4,11 +4,7 @@ use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use sev::firmware::host::*;
|
||||
use std::arch::x86_64::__cpuid;
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::io::Write;
|
||||
|
||||
const ATTESTATION_POLICY_JSON: &str = "attestation_policy.json";
|
||||
const PCR_VALUES_JSON: &str = "pcr_values.json";
|
||||
use std::fs::read_to_string;
|
||||
|
||||
const EXTENDED_FAMILY_SHIFT: u32 = 20;
|
||||
const EXTENDED_MODEL_SHIFT: u32 = 16;
|
||||
@@ -118,8 +114,18 @@ fn main() {
|
||||
.required(true)
|
||||
.value_parser(value_parser!(u64)),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pcr")
|
||||
.long("pcr")
|
||||
.value_name("FILE")
|
||||
.help("Optional path to the PCR values JSON file")
|
||||
.required(false),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
// If provided, get the PCR file path.
|
||||
let pcr_path = matches.get_one::<String>("pcr");
|
||||
|
||||
let mut firmware: Firmware = Firmware::open().unwrap();
|
||||
let status: SnpPlatformStatus = firmware.snp_platform_status().unwrap();
|
||||
|
||||
@@ -175,33 +181,27 @@ fn main() {
|
||||
let mut computation_value =
|
||||
serde_json::to_value(&computation).expect("Failed to convert computation to JSON");
|
||||
|
||||
// Read and parse the pcr_values.json file.
|
||||
let pcr_content = read_to_string(PCR_VALUES_JSON).expect("Failed to read pcr_values.json");
|
||||
let pcr_value: Value =
|
||||
serde_json::from_str(&pcr_content).expect("Failed to parse pcr_values.json");
|
||||
// If the PCR file path was provided, read and merge its JSON content.
|
||||
if let Some(pcr_path) = pcr_path {
|
||||
let pcr_content = read_to_string(pcr_path)
|
||||
.unwrap_or_else(|_| panic!("Failed to read PCR file at {}", pcr_path));
|
||||
let pcr_value: Value = serde_json::from_str(&pcr_content)
|
||||
.unwrap_or_else(|_| panic!("Failed to parse PCR JSON file at {}", pcr_path));
|
||||
|
||||
// Merge the pcr_values into the main JSON object.
|
||||
if let Value::Object(ref mut main_map) = computation_value {
|
||||
if let Value::Object(pcr_map) = pcr_value {
|
||||
// The keys in pcr_map (e.g., "pcr_values") will be added
|
||||
main_map.extend(pcr_map);
|
||||
if let Value::Object(ref mut main_map) = computation_value {
|
||||
if let Value::Object(pcr_map) = pcr_value {
|
||||
main_map.extend(pcr_map);
|
||||
} else {
|
||||
eprintln!("PCR file {} is not a JSON object.", pcr_path);
|
||||
}
|
||||
} else {
|
||||
eprintln!("{} is not a JSON object.", PCR_VALUES_JSON);
|
||||
eprintln!("The computed JSON is not an object.");
|
||||
}
|
||||
} else {
|
||||
eprintln!("The computed JSON is not an object.");
|
||||
}
|
||||
|
||||
// Serialize the merged JSON and write to file.
|
||||
let merged_json =
|
||||
serde_json::to_string_pretty(&computation_value).expect("Failed to serialize merged JSON");
|
||||
let mut file =
|
||||
File::create(ATTESTATION_POLICY_JSON).expect("Failed to create attestation policy file");
|
||||
file.write_all(merged_json.as_bytes())
|
||||
.expect("Failed to write merged JSON to file");
|
||||
|
||||
println!(
|
||||
"AttestationPolicy JSON has been written to {}",
|
||||
ATTESTATION_POLICY_JSON
|
||||
);
|
||||
println!("{}", merged_json);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user