NOISSUE - Add path to expected PCR values (#398)
CI / ci (push) Has been cancelled
Rust CI Pipeline / rust-check (push) Has been cancelled

* 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:
Danko Miladinovic
2025-03-17 12:25:42 +01:00
committed by GitHub
parent 33744a12a8
commit 293c65a3aa
22 changed files with 205 additions and 160 deletions
+17 -1
View File
@@ -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
},
}
+12 -7
View File
@@ -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)
}
+8 -7
View File
@@ -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)
}
}
})
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+2
View File
@@ -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
View File
@@ -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). | |
+43 -28
View File
@@ -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
}
+5 -3
View File
@@ -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
View File
@@ -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)
+4 -2
View File
@@ -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,
@@ -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
}
@@ -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()))
+5 -4
View File
@@ -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)
}
+1 -1
View File
@@ -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)
}
+11 -11
View File
@@ -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
}
+3 -3
View File
@@ -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)
}
})
}
+1 -1
View File
@@ -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
```
+25 -25
View File
@@ -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);
}