mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-07-21 22:38:53 -04:00
Refactor enforced/expected PCRs (#553)
* Merge enforced and expected measurements * Update measurement generation to new format * Write expected measurements hex encoded by default * Allow hex or base64 encoded expected measurements * Allow hex or base64 encoded clusterID * Allow security upgrades to warnOnly flag * Upload signed measurements in JSON format * Fetch measurements either from JSON or YAML * Use yaml.v3 instead of yaml.v2 * Error on invalid enforced selection * Add placeholder measurements to config * Update e2e test to new measurement format Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
8ce954e012
commit
f8001efbc0
46 changed files with 1180 additions and 801 deletions
|
@ -24,24 +24,24 @@ import (
|
|||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||
"github.com/edgelesssys/constellation/v2/verify/verifyproto"
|
||||
"github.com/spf13/afero"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
coordIP = flag.String("constell-ip", "", "Public IP of the Constellation")
|
||||
port = flag.String("constell-port", strconv.Itoa(constants.VerifyServiceNodePortGRPC), "NodePort of the Constellation's verification service")
|
||||
export = flag.String("o", "", "Write PCRs, formatted as Go code, to file")
|
||||
format = flag.String("format", "json", "Output format: json, yaml (default json)")
|
||||
quiet = flag.Bool("q", false, "Set to disable output")
|
||||
timeout = flag.Duration("timeout", 2*time.Minute, "Wait this duration for the verification service to become available")
|
||||
)
|
||||
|
||||
func main() {
|
||||
coordIP := flag.String("constell-ip", "", "Public IP of the Constellation")
|
||||
port := flag.String("constell-port", strconv.Itoa(constants.VerifyServiceNodePortGRPC), "NodePort of the Constellation's verification service")
|
||||
format := flag.String("format", "json", "Output format: json, yaml (default json)")
|
||||
quiet := flag.Bool("q", false, "Set to disable output")
|
||||
timeout := flag.Duration("timeout", 2*time.Minute, "Wait this duration for the verification service to become available")
|
||||
flag.Parse()
|
||||
|
||||
if *coordIP == "" || *port == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(*coordIP, *port)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
||||
defer cancel()
|
||||
|
@ -51,18 +51,13 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
pcrs, err := validatePCRAttDoc(attDocRaw)
|
||||
measurements, err := validatePCRAttDoc(attDocRaw)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
if err := printPCRs(os.Stdout, pcrs, *format); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if *export != "" {
|
||||
if err := exportToFile(*export, pcrs, &afero.Afero{Fs: afero.NewOsFs()}); err != nil {
|
||||
if err := printPCRs(os.Stdout, measurements, *format); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -104,16 +99,23 @@ func validatePCRAttDoc(attDocRaw []byte) (measurements.M, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := measurements.M{}
|
||||
for idx, pcr := range attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs {
|
||||
if len(pcr) != 32 {
|
||||
return nil, fmt.Errorf("incomplete PCR at index: %d", idx)
|
||||
}
|
||||
|
||||
m[idx] = measurements.Measurement{
|
||||
Expected: *(*[32]byte)(pcr),
|
||||
WarnOnly: true,
|
||||
}
|
||||
}
|
||||
return attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// printPCRs formates and prints PCRs to the given writer.
|
||||
// format can be one of 'json' or 'yaml'. If it doesnt match defaults to 'json'.
|
||||
// printPCRs formats and prints PCRs to the given writer.
|
||||
// format can be one of 'json' or 'yaml'. If it doesn't match defaults to 'json'.
|
||||
func printPCRs(w io.Writer, pcrs measurements.M, format string) error {
|
||||
switch format {
|
||||
case "json":
|
||||
|
@ -142,24 +144,3 @@ func printPCRsJSON(w io.Writer, pcrs measurements.M) error {
|
|||
fmt.Fprintf(w, "%s", string(pcrJSON))
|
||||
return nil
|
||||
}
|
||||
|
||||
// exportToFile writes pcrs to a file, formatted to be valid Go code.
|
||||
// Validity of the PCR map is not checked, and should be handled by the caller.
|
||||
func exportToFile(path string, pcrs measurements.M, fs *afero.Afero) error {
|
||||
goCode := `package pcrs
|
||||
|
||||
var pcrs = map[uint32][]byte{%s
|
||||
}
|
||||
`
|
||||
pcrsFormatted := ""
|
||||
for i := 0; i < len(pcrs); i++ {
|
||||
pcrHex := fmt.Sprintf("%#02X", pcrs[uint32(i)][0])
|
||||
for j := 1; j < len(pcrs[uint32(i)]); j++ {
|
||||
pcrHex = fmt.Sprintf("%s, %#02X", pcrHex, pcrs[uint32(i)][j])
|
||||
}
|
||||
|
||||
pcrsFormatted = pcrsFormatted + fmt.Sprintf("\n\t%d: {%s},", i, pcrHex)
|
||||
}
|
||||
|
||||
return fs.WriteFile(path, []byte(fmt.Sprintf(goCode, pcrsFormatted)), 0o644)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
@ -17,67 +17,13 @@ import (
|
|||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||
"github.com/google/go-tpm-tools/proto/attest"
|
||||
"github.com/google/go-tpm-tools/proto/tpm"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m,
|
||||
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
||||
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestExportToFile(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
pcrs measurements.M
|
||||
fs *afero.Afero
|
||||
wantErr bool
|
||||
}{
|
||||
"file not writeable": {
|
||||
pcrs: measurements.M{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
fs: &afero.Afero{Fs: afero.NewReadOnlyFs(afero.NewMemMapFs())},
|
||||
wantErr: true,
|
||||
},
|
||||
"file writeable": {
|
||||
pcrs: measurements.M{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
fs: &afero.Afero{Fs: afero.NewMemMapFs()},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
path := "test-file"
|
||||
err := exportToFile(path, tc.pcrs, tc.fs)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
content, err := tc.fs.ReadFile(path)
|
||||
require.NoError(err)
|
||||
|
||||
for _, pcr := range tc.pcrs {
|
||||
for _, register := range pcr {
|
||||
assert.Contains(string(content), fmt.Sprintf("%#02X", register))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
||||
|
||||
func TestValidatePCRAttDoc(t *testing.T) {
|
||||
|
@ -106,7 +52,7 @@ func TestValidatePCRAttDoc(t *testing.T) {
|
|||
{
|
||||
Pcrs: &tpm.PCRs{
|
||||
Hash: tpm.HashAlgo_SHA256,
|
||||
Pcrs: measurements.M{
|
||||
Pcrs: map[uint32][]byte{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
},
|
||||
},
|
||||
|
@ -123,8 +69,8 @@ func TestValidatePCRAttDoc(t *testing.T) {
|
|||
{
|
||||
Pcrs: &tpm.PCRs{
|
||||
Hash: tpm.HashAlgo_SHA256,
|
||||
Pcrs: measurements.M{
|
||||
0: measurements.PCRWithAllBytes(0xAA),
|
||||
Pcrs: map[uint32][]byte{
|
||||
0: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -150,7 +96,10 @@ func TestValidatePCRAttDoc(t *testing.T) {
|
|||
require.NoError(json.Unmarshal(tc.attDocRaw, &attDoc))
|
||||
qIdx, err := vtpm.GetSHA256QuoteIndex(attDoc.Attestation.Quotes)
|
||||
require.NoError(err)
|
||||
assert.EqualValues(attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs, pcrs)
|
||||
|
||||
for pcrIdx, pcrVal := range pcrs {
|
||||
assert.Equal(pcrVal.Expected[:], attDoc.Attestation.Quotes[qIdx].Pcrs.Pcrs[pcrIdx])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -164,31 +113,15 @@ func mustMarshalAttDoc(t *testing.T, attDoc vtpm.AttestationDocument) []byte {
|
|||
|
||||
func TestPrintPCRs(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
pcrs measurements.M
|
||||
format string
|
||||
}{
|
||||
"json": {
|
||||
pcrs: measurements.M{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
format: "json",
|
||||
},
|
||||
"empty format": {
|
||||
pcrs: measurements.M{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
format: "",
|
||||
},
|
||||
"yaml": {
|
||||
pcrs: measurements.M{
|
||||
0: {0x1, 0x2, 0x3},
|
||||
1: {0x1, 0x2, 0x3},
|
||||
2: {0x1, 0x2, 0x3},
|
||||
},
|
||||
format: "yaml",
|
||||
},
|
||||
}
|
||||
|
@ -197,13 +130,19 @@ func TestPrintPCRs(t *testing.T) {
|
|||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pcrs := measurements.M{
|
||||
0: measurements.WithAllBytes(0xAA, true),
|
||||
1: measurements.WithAllBytes(0xBB, true),
|
||||
2: measurements.WithAllBytes(0xCC, true),
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
err := printPCRs(&out, tc.pcrs, tc.format)
|
||||
err := printPCRs(&out, pcrs, tc.format)
|
||||
assert.NoError(err)
|
||||
|
||||
for idx, pcr := range tc.pcrs {
|
||||
for idx, pcr := range pcrs {
|
||||
assert.Contains(out.String(), fmt.Sprintf("%d", idx))
|
||||
assert.Contains(out.String(), base64.StdEncoding.EncodeToString(pcr))
|
||||
assert.Contains(out.String(), hex.EncodeToString(pcr.Expected[:]))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue