NOISSUE - Add Rust script for fetching platform data (#133)

* add rust program for fetching platform data

* fix new line error

* add CLI options to add the measurement to platform_info.json file

* add documentation for platform info testing

* add explanation for sev-snp-measure

* delete excess space

* fix minor errors

* fix minor errors

* add file permision constant
This commit is contained in:
Danko Miladinovic
2024-05-28 18:08:07 +02:00
committed by GitHub
parent 2ce112cc1b
commit 0574abc228
8 changed files with 279 additions and 5 deletions
+5 -1
View File
@@ -1,5 +1,6 @@
BUILD_DIR = build
SERVICES = manager agent cli
PLATFORM_INFO = platform_info
CGO_ENABLED ?= 0
GOARCH ?= amd64
VERSION ?= $(shell git describe --abbrev=0 --tags --always)
@@ -17,13 +18,16 @@ define compile_service
-o ${BUILD_DIR}/cocos-$(1) cmd/$(1)/main.go
endef
.PHONY: all $(SERVICES)
.PHONY: all $(SERVICES) $(PLATFORM_INFO)
all: $(SERVICES)
$(SERVICES):
$(call compile_service,$(@))
$(PLATFORM_INFO):
$(MAKE) -C ./scripts/platform_info
protoc:
protoc -I. --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative agent/agent.proto
protoc -I. --go_out=./pkg --go_opt=paths=source_relative --go-grpc_out=./pkg --go-grpc_opt=paths=source_relative manager/manager.proto
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package cli
import (
"encoding/base64"
"encoding/json"
"log"
"os"
"github.com/google/go-sev-guest/proto/check"
"github.com/spf13/cobra"
)
const filePermision = 0o755
type AttestationConfiguration struct {
SNPPolicy *check.Policy `json:"snp_policy,omitempty"`
RootOFTrust *check.RootOfTrust `json:"root_of_trust,omitempty"`
}
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 := 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)
}
},
}
}
+1 -1
View File
@@ -90,7 +90,7 @@ func main() {
attestaionCmd := cliSVC.NewAttestationCmd()
rootCmd.AddCommand(attestaionCmd)
rootCmd.AddCommand(cliSVC.NewFileHashCmd())
rootCmd.AddCommand(cliSVC.NewKeysCmd())
rootCmd.AddCommand(cliSVC.NewAddMeasurementCmd())
// Attestation commands
attestaionCmd.AddCommand(cliSVC.NewGetAttestationCmd())
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "platform_info"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sev = "3.1.1"
sysinfo = "0.30.12"
regex = "1.10.4"
+14
View File
@@ -0,0 +1,14 @@
CARGO = cargo
TARGET = target
BUILD_DIR = $(TARGET)/release
BIN_NAME = platform_info
all: build
build:
$(CARGO) build --release
clean:
$(CARGO) clean
.PHONY: all build clean
+19
View File
@@ -0,0 +1,19 @@
# Rust project for fetching platform 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.
## Usage
Clone `cocos` repository:
```bash
git clone git@github.com:ultravioletrs/cocos.git
cd ./cocos/scripts/platform_info
make
```
Then run the binary. Keep in mind that you have to specify the policy of the Guest VM:
```bash
cd ./target/releas
# Run with option --policy (policy is 64 bit number)
./platform_info --policy 196608
```
+137
View File
@@ -0,0 +1,137 @@
use clap::{Arg, Command, value_parser};
use serde::Serialize;
use std::fs::File;
use std::io::Write;
use sysinfo::System;
use regex::Regex;
use sev::firmware::host::*;
const PLATFORM_INFO_JSON : &str = "platform_info.json";
#[derive(Serialize)]
struct Vmpl {
value : u32,
}
#[derive(Serialize)]
struct SnpPolicy {
policy: u64,
family_id: Vec<u8>,
image_id: Vec<u8>,
vmpl: Vmpl,
minimum_tcb: u64,
minimum_launch_tcb: u64,
require_author_key: bool,
measurement: Vec<u8>,
host_data: Vec<u8>,
report_id_ma: Vec<u8>,
chip_id: Vec<u8>,
minimum_build: u32,
minimum_version: String,
permit_provisional_firmware: bool,
require_id_block: bool,
}
#[derive(Serialize)]
struct RootOfTrust {
product: String,
check_crl : bool,
disallow_network : bool,
}
#[derive(Serialize)]
struct Computation {
snp_policy: SnpPolicy,
root_of_trust: RootOfTrust,
}
fn get_product_name() -> String {
let mut sys = System::new_all();
sys.refresh_all();
let re = Regex::new(r"EPYC.*7..3.*").unwrap();
for cpu in sys.cpus() {
if re.is_match(cpu.brand()) {
return "Milan".to_string()
}
}
"Unknown".to_string()
}
fn get_uint64_from_tcb(tcb_version : &TcbVersion) -> u64 {
let microcode = (tcb_version.microcode as u64) << 56;
let snp = (tcb_version.snp as u64) << 48;
let tee = (tcb_version.tee as u64) << 8;
let bootloader = tcb_version.bootloader as u64;
microcode | snp | tee | bootloader
}
fn main() {
let matches = Command::new("Platform info")
.about("Processes command line options and outputs a JSON file for Attestation verification")
.arg(Arg::new("policy")
.long("policy")
.value_name("INT")
.help("Sets the policy integer")
.required(true)
.value_parser(value_parser!(u64)))
.get_matches();
let mut firmware: Firmware = Firmware::open().unwrap();
let status: SnpPlatformStatus = firmware.snp_platform_status().unwrap();
let policy: u64 = *matches.get_one::<u64>("policy").unwrap();
let family_id = vec![0];
let image_id = vec![0];
let vmpl = Vmpl { value: 0};
let minimum_tcb = get_uint64_from_tcb(&status.platform_tcb_version);
let minimum_launch_tcb = get_uint64_from_tcb(&status.platform_tcb_version);
let require_author_key = false;
let measurement = vec![0];
let host_data = vec![0];
let report_id_ma = vec![0];
let cpu_id: Identifier = firmware.get_identifier().unwrap();
let chip_id: Vec<u8> = cpu_id.0;
let minimum_build = status.build_id;
let minimum_version = status.version.to_string();
let permit_provisional_firmware = false;
let require_id_block = false;
let snp_policy = SnpPolicy {
policy,
family_id,
image_id,
vmpl,
minimum_tcb,
minimum_launch_tcb,
require_author_key,
measurement,
host_data,
report_id_ma,
chip_id,
minimum_build,
minimum_version,
permit_provisional_firmware,
require_id_block,
};
let root_of_trust = RootOfTrust {
product : get_product_name(),
check_crl : true,
disallow_network : false,
};
let computation = Computation {
snp_policy,
root_of_trust,
};
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");
file.write_all(json.as_bytes()).expect("Failed to write to file");
println!("Computation JSON has been written to {}", PLATFORM_INFO_JSON);
}
+27 -3
View File
@@ -17,13 +17,37 @@ The algorithm program should return the results to a socket and an example can b
Agent is started automatically in the VM when launched but requires configuration and manifest to be passed by manager. Alternatively you can pass configuration using this [simplified script](./agent-config/main.go)
Open console on the host, and run
For attested TLS, you will have to calculate the VM's measurement, which can be done using a tool [sev-snp-measure](https://pypi.org/project/sev-snp-measure/).
```bash
# Define the path to the OVMF, KERNEL, INITRD and CMD Kernel line arguments.
OVMF_CODE="/home/cocosai/ovmf/Build/AmdSev/DEBUG_GCC5/FV/OVMF.fd"
INITRD="/home/cocosai/initramfs.cpio.gz"
KERNEL="/home/cocosai/bzImage"
LINE="earlyprintk=serial console=ttyS0"
# Call sev-snp-measure
sev-snp-measure --mode snp --vcpus 4 --vcpu-type EPYC-v4 --ovmf $OVMF_CODE --kernel $KERNEL --initrd $INITRD --append "$LINE" --output-format base64
```
```sh
export AGENT_GRPC_URL=localhost:7002
# For attested TLS, also define the path to the computation.json that contains reference values for the fields of the attestation report
export AGENT_GRPC_MANIFEST=./test/manual/computation/computation.json
# 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
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.
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
export AGENT_GRPC_ATTESTED_TLS=true
# Retieve Attestation