mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-07-19 21:38:44 -04:00
Add new generate measurements matrix CI/CD action (now with AWS support) (#641)
This commit is contained in:
parent
6af54142f2
commit
89b25f8ebb
11 changed files with 533 additions and 322 deletions
143
hack/pcr-compare/main.go
Normal file
143
hack/pcr-compare/main.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 3 {
|
||||
if len(os.Args) == 0 {
|
||||
fmt.Println("Usage:", "pcr-compare", "<expected-measurements> <actual-measurements>")
|
||||
} else {
|
||||
fmt.Println("Usage:", os.Args[0], "<expected-measurements> <actual-measurements>")
|
||||
}
|
||||
fmt.Println("<expected-measurements> is supposed to be a JSON file from the 'Build OS image' pipeline.")
|
||||
fmt.Println("<actual-measurements> in supposed to be a JSON file with metadata from the PCR reader which is supposed to be verified.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
parsedExpectedMeasurements, err := parseMeasurements(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
parsedActualMeasurements, err := parseMeasurements(os.Args[2])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Extract the PCR values we care about.
|
||||
strippedActualMeasurements := stripMeasurements(parsedActualMeasurements)
|
||||
strippedExpectedMeasurements := stripMeasurements(parsedExpectedMeasurements)
|
||||
|
||||
// Do the check early.
|
||||
areEqual := strippedExpectedMeasurements.EqualTo(strippedActualMeasurements)
|
||||
|
||||
// Print values and similarities / differences in addition.
|
||||
compareMeasurements(strippedExpectedMeasurements, strippedActualMeasurements)
|
||||
|
||||
if !areEqual {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// parseMeasurements unmarshals a JSON file containing the expected or actual measurements.
|
||||
func parseMeasurements(filename string) (measurements.M, error) {
|
||||
fileData, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return measurements.M{}, err
|
||||
}
|
||||
|
||||
// Technically the expected version does not hold metadata, but we can use the same struct as both hold the measurements in `measurements`.
|
||||
// This uses the fallback mechanism of the Measurements unmarshaller which accepts strings without the full struct, defaulting to warnOnly = false.
|
||||
// warnOnly = false is expected for the expected measurements, so that's fine.
|
||||
// We don't verify metadata here, the CLI has to do that.
|
||||
var parsedMeasurements measurements.WithMetadata
|
||||
if err := json.Unmarshal(fileData, &parsedMeasurements); err != nil {
|
||||
return measurements.M{}, err
|
||||
}
|
||||
|
||||
return parsedMeasurements.Measurements, nil
|
||||
}
|
||||
|
||||
// stripMeasurements extracts only the measurements we want to compare.
|
||||
// This excludes PCR 15 since the actual measurements come from an initialized cluster, but the expected measurements are supposed to be from an uninitialized state.
|
||||
func stripMeasurements(input measurements.M) measurements.M {
|
||||
toBeChecked := []uint32{4, 8, 9, 11, 12, 13}
|
||||
|
||||
strippedMeasurements := make(measurements.M, len(toBeChecked))
|
||||
for _, pcr := range toBeChecked {
|
||||
if _, ok := input[pcr]; ok {
|
||||
strippedMeasurements[pcr] = input[pcr]
|
||||
}
|
||||
}
|
||||
|
||||
return strippedMeasurements
|
||||
}
|
||||
|
||||
// compareMeasurements compares the expected PCRs with the actual PCRs.
|
||||
func compareMeasurements(expectedMeasurements, actualMeasurements measurements.M) {
|
||||
redPrint := color.New(color.FgRed).SprintFunc()
|
||||
greenPrint := color.New(color.FgGreen).SprintFunc()
|
||||
|
||||
expectedPCRs := getSortedKeysOfMap(expectedMeasurements)
|
||||
for _, pcr := range expectedPCRs {
|
||||
if _, ok := actualMeasurements[pcr]; !ok {
|
||||
color.Magenta("Expected PCR %d not found in the calculated measurements.\n", pcr)
|
||||
continue
|
||||
}
|
||||
|
||||
actualValue := actualMeasurements[pcr]
|
||||
expectedValue := expectedMeasurements[pcr]
|
||||
|
||||
fmt.Printf("Expected PCR %02d: %s (warnOnly: %t)\n", pcr, hex.EncodeToString(expectedValue.Expected[:]), expectedValue.WarnOnly)
|
||||
|
||||
var foundMismatch bool
|
||||
var coloredValue string
|
||||
var coloredWarnOnly string
|
||||
if actualValue.Expected == expectedValue.Expected {
|
||||
coloredValue = greenPrint(hex.EncodeToString(actualValue.Expected[:]))
|
||||
} else {
|
||||
coloredValue = redPrint(hex.EncodeToString(actualValue.Expected[:]))
|
||||
foundMismatch = true
|
||||
}
|
||||
|
||||
if actualValue.WarnOnly == expectedValue.WarnOnly {
|
||||
coloredWarnOnly = greenPrint(fmt.Sprintf("%t", actualValue.WarnOnly))
|
||||
} else {
|
||||
coloredWarnOnly = redPrint(fmt.Sprintf("%t", actualValue.WarnOnly))
|
||||
foundMismatch = true
|
||||
}
|
||||
|
||||
fmt.Printf("Measured PCR %02d: %s (warnOnly: %s)\n", pcr, coloredValue, coloredWarnOnly)
|
||||
if !foundMismatch {
|
||||
color.Green("PCR %02d matches.\n", pcr)
|
||||
} else {
|
||||
color.Red("PCR %02d does not match.\n", pcr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getSortedKeysOfMap returns the sorted keys of a map to allow the PCR output to be ordered in the output.
|
||||
func getSortedKeysOfMap(inputMap measurements.M) []uint32 {
|
||||
keys := make([]uint32, 0, len(inputMap))
|
||||
for singleKey := range inputMap {
|
||||
keys = append(keys, singleKey)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
||||
|
||||
return keys
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue