mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
COCOS-153 - Add host-data option (#163)
* add host_data option * add CLI hostdata option and rename platform to backend * move code for computation hash to a function * rename getComputationHash to computationHash * add default for backend information field switch
This commit is contained in:
committed by
GitHub
parent
0e236bf2e8
commit
006897a57c
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
|
||||
const (
|
||||
measurementField fieldType = iota
|
||||
hostDataField
|
||||
)
|
||||
|
||||
const (
|
||||
// 0o744 file permission gives RWX permission to the user and only the R permission to others.
|
||||
filePermission = 0o744
|
||||
// Length of the expected host data and measurement field in bytes.
|
||||
hostDataLength = 32
|
||||
measurementLength = 48
|
||||
)
|
||||
|
||||
var (
|
||||
errDecode = errors.New("base64 string could not be decoded")
|
||||
errDataLength = errors.New("data does not have an adequate length")
|
||||
errReadingBackendInfoFile = errors.New("error while reading the backend information file")
|
||||
errUnmarshalJSON = errors.New("failed to unmarshal json")
|
||||
errMarshalJSON = errors.New("failed to marshal json")
|
||||
errWriteFile = errors.New("failed to write to file")
|
||||
errBackendField = errors.New("the specified field type does not exist in the backend information")
|
||||
)
|
||||
|
||||
type AttestationConfiguration struct {
|
||||
SNPPolicy *check.Policy `json:"snp_policy,omitempty"`
|
||||
RootOfTrust *check.RootOfTrust `json:"root_of_trust,omitempty"`
|
||||
}
|
||||
|
||||
func (cli *CLI) NewBackendCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "backend [command]",
|
||||
Short: "Change backend information",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Change backend information\n\n")
|
||||
fmt.Printf("Usage:\n %s [command]\n\n", cmd.CommandPath())
|
||||
fmt.Printf("Available Commands:\n")
|
||||
|
||||
// Filter out "completion" command
|
||||
availableCommands := make([]*cobra.Command, 0)
|
||||
for _, subCmd := range cmd.Commands() {
|
||||
if subCmd.Name() != "completion" {
|
||||
availableCommands = append(availableCommands, subCmd)
|
||||
}
|
||||
}
|
||||
|
||||
for _, subCmd := range availableCommands {
|
||||
fmt.Printf(" %-15s%s\n", subCmd.Name(), subCmd.Short)
|
||||
}
|
||||
|
||||
fmt.Printf("\nFlags:\n")
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
fmt.Printf(" -%s, --%s %s\n", flag.Shorthand, flag.Name, flag.Usage)
|
||||
})
|
||||
fmt.Printf("\nUse \"%s [command] --help\" for more information about a command.\n", cmd.CommandPath())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *CLI) NewAddMeasurementCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "measurement",
|
||||
Short: "Add measurement to the backend info file. The value should be in base64. The second parameter is backend_info.json file",
|
||||
Example: "measurement <measurement> <backend_info.json>",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := changeAttestationConfiguration(args[1], args[0], measurementLength, measurementField); err != nil {
|
||||
log.Fatalf("Error could not change measurement data %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *CLI) NewAddHostDataCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "hostdata",
|
||||
Short: "Add host data to the backend info file. The value should be in base64. The second parameter is backend_info.json file",
|
||||
Example: "hostdata <host-data> <backend_info.json>",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := changeAttestationConfiguration(args[1], args[0], hostDataLength, hostDataField); err != nil {
|
||||
log.Fatalf("Error could not change host data %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func changeAttestationConfiguration(fileName string, base64Data string, expectedLength int, field fieldType) error {
|
||||
data, err := base64.StdEncoding.DecodeString(base64Data)
|
||||
if err != nil {
|
||||
return errDecode
|
||||
}
|
||||
|
||||
if len(data) != expectedLength {
|
||||
return errDataLength
|
||||
}
|
||||
|
||||
ac := AttestationConfiguration{}
|
||||
|
||||
backendInfo, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return errors.Wrap(errReadingBackendInfoFile, err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(backendInfo, &ac); err != nil {
|
||||
return errors.Wrap(errUnmarshalJSON, err)
|
||||
}
|
||||
|
||||
switch field {
|
||||
case measurementField:
|
||||
ac.SNPPolicy.Measurement = data
|
||||
case hostDataField:
|
||||
ac.SNPPolicy.HostData = data
|
||||
default:
|
||||
return errBackendField
|
||||
}
|
||||
|
||||
fileJson, err := json.MarshalIndent(ac, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(errMarshalJSON, err)
|
||||
}
|
||||
if err = os.WriteFile(fileName, fileJson, filePermission); err != nil {
|
||||
return errors.Wrap(errWriteFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
|
||||
)
|
||||
|
||||
const filePermision = 0o644
|
||||
|
||||
func (cli *CLI) NewAddMeasurementCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "measurement",
|
||||
Short: "Add measurement to the platform info file. The value should be in base64. The second parameter is platform_info.json file",
|
||||
Example: "measurement <measurement> <platform_info.json>",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
measurement, err := base64.StdEncoding.DecodeString(args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("Error could not decode base64: %v", err)
|
||||
}
|
||||
|
||||
attestationConfiguration := grpc.AttestationConfiguration{}
|
||||
|
||||
manifest, err := os.OpenFile(args[1], os.O_RDWR, filePermision)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening the platform information file: %v", err)
|
||||
}
|
||||
defer manifest.Close()
|
||||
|
||||
decoder := json.NewDecoder(manifest)
|
||||
err = decoder.Decode(&attestationConfiguration)
|
||||
if err != nil {
|
||||
log.Fatalf("Error decoding the platform information file: %v", err)
|
||||
}
|
||||
|
||||
attestationConfiguration.SNPPolicy.Measurement = measurement
|
||||
if err = manifest.Truncate(0); err != nil {
|
||||
log.Fatalf("Error could not truncate platform information JSON file: %v", err)
|
||||
}
|
||||
|
||||
fileJson, err := json.MarshalIndent(attestationConfiguration, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("Error marshaling the platform information JSON: %v", err)
|
||||
}
|
||||
if err = os.WriteFile(manifest.Name(), fileJson, filePermision); err != nil {
|
||||
log.Fatalf("Error writing into platform information JSON file: %v", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -102,6 +102,7 @@ func main() {
|
||||
|
||||
keysCmd := cliSVC.NewKeysCmd()
|
||||
attestationCmd := cliSVC.NewAttestationCmd()
|
||||
backendCmd := cliSVC.NewBackendCmd()
|
||||
|
||||
// Agent Commands
|
||||
rootCmd.AddCommand(cliSVC.NewAlgorithmCmd())
|
||||
@@ -109,7 +110,7 @@ func main() {
|
||||
rootCmd.AddCommand(cliSVC.NewResultsCmd())
|
||||
rootCmd.AddCommand(attestationCmd)
|
||||
rootCmd.AddCommand(cliSVC.NewFileHashCmd())
|
||||
rootCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
|
||||
rootCmd.AddCommand(backendCmd)
|
||||
rootCmd.AddCommand(keysCmd)
|
||||
rootCmd.AddCommand(cliSVC.NewCABundleCmd(directoryCachePath))
|
||||
|
||||
@@ -126,6 +127,10 @@ func main() {
|
||||
"User Key type",
|
||||
)
|
||||
|
||||
// Backend information commands
|
||||
backendCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
|
||||
backendCmd.AddCommand(cliSVC.NewAddHostDataCmd())
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
logger.Error(fmt.Sprintf("Command execution failed: %s", err))
|
||||
return
|
||||
|
||||
@@ -50,6 +50,7 @@ type SevConfig struct {
|
||||
ID string `env:"SEV_ID" envDefault:"sev0"`
|
||||
CBitPos int `env:"SEV_CBITPOS" envDefault:"51"`
|
||||
ReducedPhysBits int `env:"SEV_REDUCED_PHYS_BITS" envDefault:"1"`
|
||||
HostData string `env:"HOST_DATA" envDefault:""`
|
||||
}
|
||||
|
||||
type VSockConfig struct {
|
||||
@@ -177,9 +178,14 @@ func constructQemuArgs(config Config) []string {
|
||||
if config.EnableSEV || config.EnableSEVSNP {
|
||||
sevType := "sev-guest"
|
||||
kernelHash := ""
|
||||
hostData := ""
|
||||
|
||||
if config.EnableSEVSNP {
|
||||
sevType = "sev-snp-guest"
|
||||
|
||||
if config.SevConfig.HostData != "" {
|
||||
hostData = fmt.Sprintf(",host-data=%s", config.SevConfig.HostData)
|
||||
}
|
||||
}
|
||||
|
||||
if config.KernelHash {
|
||||
@@ -187,12 +193,13 @@ func constructQemuArgs(config Config) []string {
|
||||
}
|
||||
|
||||
args = append(args, "-object",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s",
|
||||
fmt.Sprintf("%s,id=%s,cbitpos=%d,reduced-phys-bits=%d%s%s",
|
||||
sevType,
|
||||
config.SevConfig.ID,
|
||||
config.SevConfig.CBitPos,
|
||||
config.SevConfig.ReducedPhysBits,
|
||||
kernelHash))
|
||||
kernelHash,
|
||||
hostData))
|
||||
|
||||
args = append(args, "-machine",
|
||||
fmt.Sprintf("memory-encryption=%s", config.SevConfig.ID))
|
||||
|
||||
@@ -4,6 +4,7 @@ package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/ultravioletrs/cocos/agent"
|
||||
"github.com/ultravioletrs/cocos/manager/qemu"
|
||||
"github.com/ultravioletrs/cocos/pkg/manager"
|
||||
"golang.org/x/crypto/sha3"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
@@ -36,6 +38,9 @@ var (
|
||||
ErrFailedToAllocatePort = errors.New("failed to allocate free port on host")
|
||||
|
||||
errInvalidHashLength = errors.New("hash must be of byte length 32")
|
||||
|
||||
// ErrFailedToCalculateHash indicates that agent computation returned an error while calculating the hash of the computation.
|
||||
ErrFailedToCalculateHash = errors.New("error while calculating the hash of the computation")
|
||||
)
|
||||
|
||||
// Service specifies an API that must be fulfilled by the domain service
|
||||
@@ -104,6 +109,15 @@ func (ms *managerService) Run(ctx context.Context, c *manager.ComputationRunReq)
|
||||
}
|
||||
ms.qemuCfg.HostFwdAgent = agentPort
|
||||
|
||||
ch, err := computationHash(ac)
|
||||
if err != nil {
|
||||
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
|
||||
return "", errors.Wrap(ErrFailedToCalculateHash, err)
|
||||
}
|
||||
|
||||
// Define host-data value of QEMU for SEV-SNP, with a base64 encoding of the computation hash.
|
||||
ms.qemuCfg.SevConfig.HostData = base64.StdEncoding.EncodeToString(ch[:])
|
||||
|
||||
ms.publishEvent("vm-provision", c.Id, "in-progress", json.RawMessage{})
|
||||
if _, err = qemu.CreateVM(ctx, ms.qemuCfg); err != nil {
|
||||
ms.publishEvent("vm-provision", c.Id, "failed", json.RawMessage{})
|
||||
@@ -156,3 +170,12 @@ func (ms *managerService) publishEvent(event, cmpID, status string, details json
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func computationHash(ac agent.Computation) ([32]byte, error) {
|
||||
jsonData, err := json.Marshal(ac)
|
||||
if err != nil {
|
||||
return [32]byte{}, err
|
||||
}
|
||||
|
||||
return sha3.Sum256(jsonData), nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "platform_info"
|
||||
name = "backend_info"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
CARGO = cargo
|
||||
TARGET = target
|
||||
BUILD_DIR = $(TARGET)/release
|
||||
BIN_NAME = platform_info
|
||||
BIN_NAME = backend_info
|
||||
|
||||
all: build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Rust project for fetching platform info
|
||||
# Rust project for fetching backend info
|
||||
This rust project fetches information from the host system needed for validation of the attestation report. It outputs a JSON file that contains the said information.
|
||||
The JSON file is in a format that can be used with the [go-sev-guest](https://github.com/google/go-sev-guest) library.
|
||||
|
||||
@@ -6,7 +6,7 @@ The JSON file is in a format that can be used with the [go-sev-guest](https://gi
|
||||
Clone `cocos` repository:
|
||||
```bash
|
||||
git clone git@github.com:ultravioletrs/cocos.git
|
||||
cd ./cocos/scripts/platform_info
|
||||
cd ./cocos/scripts/backend_info
|
||||
make
|
||||
```
|
||||
|
||||
@@ -15,5 +15,5 @@ Then run the binary. Keep in mind that you have to specify the policy of the Gue
|
||||
cd ./target/releas
|
||||
|
||||
# Run with option --policy (policy is 64 bit number)
|
||||
./platform_info --policy 196608
|
||||
./backend_info --policy 196608
|
||||
```
|
||||
@@ -6,7 +6,7 @@ use sysinfo::System;
|
||||
use regex::Regex;
|
||||
use sev::firmware::host::*;
|
||||
|
||||
const PLATFORM_INFO_JSON : &str = "platform_info.json";
|
||||
const BACKEND_INFO_JSON : &str = "backend_info.json";
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Vmpl {
|
||||
@@ -70,7 +70,7 @@ fn get_uint64_from_tcb(tcb_version : &TcbVersion) -> u64 {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = Command::new("Platform info")
|
||||
let matches = Command::new("Backend info")
|
||||
.about("Processes command line options and outputs a JSON file for Attestation verification")
|
||||
.arg(Arg::new("policy")
|
||||
.long("policy")
|
||||
@@ -130,8 +130,8 @@ fn main() {
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&computation).expect("Failed to serialize to JSON");
|
||||
let mut file = File::create(PLATFORM_INFO_JSON).expect("Failed to create file");
|
||||
let mut file = File::create(BACKEND_INFO_JSON).expect("Failed to create file");
|
||||
file.write_all(json.as_bytes()).expect("Failed to write to file");
|
||||
|
||||
println!("Computation JSON has been written to {}", PLATFORM_INFO_JSON);
|
||||
println!("Computation JSON has been written to {}", BACKEND_INFO_JSON);
|
||||
}
|
||||
+20
-12
@@ -39,21 +39,29 @@ In the following text, we can see an example of how the CLI tool is used.
|
||||
```bash
|
||||
export AGENT_GRPC_URL=localhost:7002
|
||||
|
||||
# For attested TLS, the CLI should also be aware of the VM measurement. To
|
||||
# add the measurement to the .json file that contains the information about
|
||||
# the platform, run CLI with the measurement in base64 format and the path
|
||||
# of the platform_info.json file.:
|
||||
go run cmd/cli/main.go measurement '<measurement>' '<platform_info.json>'
|
||||
|
||||
# The platform_info.json file can be generated using Rust by running:
|
||||
cd scripts/platform_info
|
||||
# For attested TLS, the CLI needs a file containing the necessary information
|
||||
# about the SEV-SNP capable backend. This information will be used to verify
|
||||
# the attestation report received from the agent.
|
||||
# The backend_info.json file can be generated using Rust by running:
|
||||
cd scripts/backend_info
|
||||
make
|
||||
sudo ./target/release/platform_info --policy 196608 # Default value of the policy should be 196608
|
||||
# The output file platform_info.json will be generated in the directory from which the executable has been called.
|
||||
sudo ./target/release/backend_info --policy 196608 # Default value of the policy should be 196608
|
||||
# The output file backend_info.json will be generated in the directory from which the executable has been called.
|
||||
cd ../..
|
||||
|
||||
# For attested TLS, also define the path to the platform_info.json that contains reference values for the fields of the attestation report
|
||||
export AGENT_GRPC_MANIFEST=./scripts/platform_info/platform_info.json
|
||||
# The CLI should also be aware of the VM measurement. To add the measurement
|
||||
# to the .json file that contains the information about the platform, run CLI
|
||||
# with the measurement in base64 format and the path of the backend_info.json file.:
|
||||
go run cmd/cli/main.go backend measurement '<measurement>' '<backend_info.json>'
|
||||
|
||||
# If the VM is booted with the QEMU host data option, the CLI should also know
|
||||
# the host data information. To add the host data to the .json file that contains
|
||||
# the information about the platform, run CLI with the host data in base64 format
|
||||
# and the path of the backend_info.json file.:
|
||||
go run cmd/cli/main.go backend measurement '<host-data>' '<backend_info.json>'
|
||||
|
||||
# For attested TLS, also define the path to the backend_info.json that contains reference values for the fields of the attestation report
|
||||
export AGENT_GRPC_MANIFEST=./scripts/backend_info/backend_info.json
|
||||
export AGENT_GRPC_ATTESTED_TLS=true
|
||||
|
||||
# Retieve Attestation
|
||||
|
||||
Reference in New Issue
Block a user