From 614a99c6f811ccace5e86ab58fb599e2ebc963d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Wei=C3=9Fe?= Date: Thu, 20 Jun 2024 15:58:53 +0200 Subject: [PATCH 1/2] manifest: allow null fields in json --- .../internal/authority/authority_test.go | 10 +-- internal/manifest/constants.go | 12 ++-- internal/manifest/manifest.go | 29 +++++++-- internal/manifest/manifest_test.go | 61 +++++++++++++++++++ 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/coordinator/internal/authority/authority_test.go b/coordinator/internal/authority/authority_test.go index 2fac9c108f..445eaab1ca 100644 --- a/coordinator/internal/authority/authority_test.go +++ b/coordinator/internal/authority/authority_test.go @@ -91,13 +91,13 @@ func newManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) { policy := []byte("=== SOME REGO HERE ===") policyHash := sha256.Sum256(policy) policyHashHex := manifest.NewHexString(policyHash[:]) - mnfst := &manifest.Manifest{ - Policies: map[manifest.HexString][]string{policyHashHex: {"test"}}, - WorkloadOwnerKeyDigests: []manifest.HexString{keyDigest}, - } + + mnfst := manifest.Default() + mnfst.Policies = map[manifest.HexString][]string{policyHashHex: {"test"}} + mnfst.WorkloadOwnerKeyDigests = []manifest.HexString{keyDigest} mnfstBytes, err := json.Marshal(mnfst) require.NoError(t, err) - return mnfst, mnfstBytes, [][]byte{policy} + return &mnfst, mnfstBytes, [][]byte{policy} } func requireGauge(t *testing.T, reg *prometheus.Registry, val int) { diff --git a/internal/manifest/constants.go b/internal/manifest/constants.go index c5cab58b14..258d36f69f 100644 --- a/internal/manifest/constants.go +++ b/internal/manifest/constants.go @@ -12,13 +12,17 @@ func Default() Manifest { ReferenceValues: ReferenceValues{ SNP: SNPReferenceValues{ MinimumTCB: SNPTCB{ - BootloaderVersion: 3, - TEEVersion: 0, - SNPVersion: 8, - MicrocodeVersion: 115, + BootloaderVersion: toPtr(SVN(3)), + TEEVersion: toPtr(SVN(0)), + SNPVersion: toPtr(SVN(8)), + MicrocodeVersion: toPtr(SVN(115)), }, }, TrustedMeasurement: HexString(trustedMeasurement), }, } } + +func toPtr[T any](t T) *T { + return &t +} diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index fd28681c96..0e42b974ce 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -38,18 +38,18 @@ type SNPReferenceValues struct { // SNPTCB represents a set of SNP TCB values. type SNPTCB struct { - BootloaderVersion SVN - TEEVersion SVN - SNPVersion SVN - MicrocodeVersion SVN + BootloaderVersion *SVN + TEEVersion *SVN + SNPVersion *SVN + MicrocodeVersion *SVN } // SVN is a SNP secure version number. type SVN uint8 // UInt8 returns the uint8 value of the SVN. -func (s SVN) UInt8() uint8 { - return uint8(s) +func (s *SVN) UInt8() uint8 { + return uint8(*s) } // MarshalJSON marshals the SVN to JSON. @@ -137,6 +137,10 @@ func (m *Manifest) SNPValidateOpts() (*validate.Options, error) { trustedMeasurement = make([]byte, 48) } + if err = checkNullFields(m.ReferenceValues.SNP.MinimumTCB); err != nil { + return nil, err + } + return &validate.Options{ Measurement: trustedMeasurement, GuestPolicy: abi.SnpPolicy{ @@ -159,3 +163,16 @@ func (m *Manifest) SNPValidateOpts() (*validate.Options, error) { PermitProvisionalFirmware: true, }, nil } + +func checkNullFields(tcb SNPTCB) error { + if tcb.BootloaderVersion == nil { + return fmt.Errorf("field BootloaderVersion in manifest cannot be empty") + } else if tcb.TEEVersion == nil { + return fmt.Errorf("field TEEVersion in manifest cannot be empty") + } else if tcb.SNPVersion == nil { + return fmt.Errorf("field SNPVersion in manifest cannot be empty") + } else if tcb.MicrocodeVersion == nil { + return fmt.Errorf("field MicrocodeVersion in manifest cannot be empty") + } + return nil +} diff --git a/internal/manifest/manifest_test.go b/internal/manifest/manifest_test.go index ce60390cbf..6ba76e39d0 100644 --- a/internal/manifest/manifest_test.go +++ b/internal/manifest/manifest_test.go @@ -9,6 +9,7 @@ import ( "strconv" "testing" + "github.com/google/go-sev-guest/kds" "github.com/stretchr/testify/assert" ) @@ -145,3 +146,63 @@ func TestPolicy(t *testing.T) { assert.Error(err) }) } + +func TestSNPValidateOpts(t *testing.T) { + testCases := []struct { + tcb SNPTCB + tm HexString + wantErr bool + }{ + { + tcb: SNPTCB{ + BootloaderVersion: toPtr(SVN(0)), + TEEVersion: toPtr(SVN(1)), + SNPVersion: toPtr(SVN(2)), + MicrocodeVersion: toPtr(SVN(3)), + }, + tm: HexString("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + tcb: SNPTCB{}, + wantErr: true, + }, + } + + for i, tc := range testCases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + assert := assert.New(t) + + mnfst := Manifest{ + ReferenceValues: ReferenceValues{ + SNP: SNPReferenceValues{MinimumTCB: tc.tcb}, + TrustedMeasurement: tc.tm, + }, + } + + opts, err := mnfst.SNPValidateOpts() + if tc.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + + assert.NotNil(tc.tcb.BootloaderVersion) + assert.NotNil(tc.tcb.TEEVersion) + assert.NotNil(tc.tcb.SNPVersion) + assert.NotNil(tc.tcb.MicrocodeVersion) + + trustedMeasurement, err := tc.tm.Bytes() + assert.NoError(err) + assert.Equal(trustedMeasurement, opts.Measurement) + + tcbParts := kds.TCBParts{ + BlSpl: tc.tcb.BootloaderVersion.UInt8(), + TeeSpl: tc.tcb.TEEVersion.UInt8(), + SnpSpl: tc.tcb.SNPVersion.UInt8(), + UcodeSpl: tc.tcb.MicrocodeVersion.UInt8(), + } + assert.Equal(tcbParts, opts.MinimumTCB) + assert.Equal(tcbParts, opts.MinimumLaunchTCB) + }) + } +} From 41a4804890f729d16e5b6974ba54c0d4df4b2e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Wei=C3=9Fe?= Date: Thu, 20 Jun 2024 16:41:07 +0200 Subject: [PATCH 2/2] generate: add flag for aks reference values The manifest will be generated with invalid null values when the flag is not specified which must be filled out by the user. --- cli/cmd/generate.go | 13 +++++++++++ .../internal/authority/authority_test.go | 2 +- e2e/internal/contrasttest/contrasttest.go | 7 +++++- internal/manifest/constants.go | 22 ++++++++++++------- justfile | 1 + 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/cli/cmd/generate.go b/cli/cmd/generate.go index e4b3723665..26959a27b9 100644 --- a/cli/cmd/generate.go +++ b/cli/cmd/generate.go @@ -63,6 +63,7 @@ subcommands.`, cmd.Flags().StringP("policy", "p", rulesFilename, "path to policy (.rego) file") cmd.Flags().StringP("settings", "s", settingsFilename, "path to settings (.json) file") cmd.Flags().StringP("manifest", "m", manifestFilename, "path to manifest (.json) file") + cmd.Flags().String("reference-values", "", "set the default reference values used for attestation (one of: aks)") cmd.Flags().StringArrayP("workload-owner-key", "w", []string{workloadOwnerPEM}, "path to workload owner key (.pem) file") cmd.Flags().BoolP("disable-updates", "d", false, "prevent further updates of the manifest") cmd.Flags().String("image-replacements", "", "path to image replacements file") @@ -115,6 +116,9 @@ func runGenerate(cmd *cobra.Command, args []string) error { } defaultManifest := manifest.Default() + if flags.referenceValues == "aks" { + defaultManifest = manifest.DefaultAKS() + } defaultManifestData, err := json.MarshalIndent(&defaultManifest, "", " ") if err != nil { return fmt.Errorf("marshaling default manifest: %w", err) @@ -445,6 +449,7 @@ type generateFlags struct { policyPath string settingsPath string manifestPath string + referenceValues string workloadOwnerKeys []string disableUpdates bool workspaceDir string @@ -465,6 +470,13 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { if err != nil { return nil, err } + referenceValues, err := cmd.Flags().GetString("reference-values") + if err != nil { + return nil, err + } + if !slices.Contains([]string{"", "aks"}, referenceValues) { + return nil, fmt.Errorf("unknown reference values") + } workloadOwnerKeys, err := cmd.Flags().GetStringArray("workload-owner-key") if err != nil { return nil, err @@ -507,6 +519,7 @@ func parseGenerateFlags(cmd *cobra.Command) (*generateFlags, error) { policyPath: policyPath, settingsPath: settingsPath, manifestPath: manifestPath, + referenceValues: referenceValues, workloadOwnerKeys: workloadOwnerKeys, disableUpdates: disableUpdates, workspaceDir: workspaceDir, diff --git a/coordinator/internal/authority/authority_test.go b/coordinator/internal/authority/authority_test.go index 445eaab1ca..ba29ab593a 100644 --- a/coordinator/internal/authority/authority_test.go +++ b/coordinator/internal/authority/authority_test.go @@ -92,7 +92,7 @@ func newManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) { policyHash := sha256.Sum256(policy) policyHashHex := manifest.NewHexString(policyHash[:]) - mnfst := manifest.Default() + mnfst := manifest.DefaultAKS() mnfst.Policies = map[manifest.HexString][]string{policyHashHex: {"test"}} mnfst.WorkloadOwnerKeyDigests = []manifest.HexString{keyDigest} mnfstBytes, err := json.Marshal(mnfst) diff --git a/e2e/internal/contrasttest/contrasttest.go b/e2e/internal/contrasttest/contrasttest.go index 630891ac4e..9209a7812e 100644 --- a/e2e/internal/contrasttest/contrasttest.go +++ b/e2e/internal/contrasttest/contrasttest.go @@ -133,7 +133,12 @@ func (ct *ContrastTest) Init(t *testing.T, resources []any) { func (ct *ContrastTest) Generate(t *testing.T) { require := require.New(t) - args := append(ct.commonArgs(), "--image-replacements", ct.ImageReplacementsFile, path.Join(ct.WorkDir, "resources.yaml")) + args := append( + ct.commonArgs(), + "--image-replacements", ct.ImageReplacementsFile, + "--reference-values", "aks", + path.Join(ct.WorkDir, "resources.yaml"), + ) generate := cmd.NewGenerateCmd() generate.Flags().String("workspace-dir", "", "") // Make generate aware of root flags diff --git a/internal/manifest/constants.go b/internal/manifest/constants.go index 258d36f69f..79497a7fa8 100644 --- a/internal/manifest/constants.go +++ b/internal/manifest/constants.go @@ -10,19 +10,25 @@ var trustedMeasurement = "000000000000000000000000000000000000000000000000000000 func Default() Manifest { return Manifest{ ReferenceValues: ReferenceValues{ - SNP: SNPReferenceValues{ - MinimumTCB: SNPTCB{ - BootloaderVersion: toPtr(SVN(3)), - TEEVersion: toPtr(SVN(0)), - SNPVersion: toPtr(SVN(8)), - MicrocodeVersion: toPtr(SVN(115)), - }, - }, TrustedMeasurement: HexString(trustedMeasurement), }, } } +// DefaultAKS returns a default manifest with AKS reference values. +func DefaultAKS() Manifest { + mnfst := Default() + mnfst.ReferenceValues.SNP = SNPReferenceValues{ + MinimumTCB: SNPTCB{ + BootloaderVersion: toPtr(SVN(3)), + TEEVersion: toPtr(SVN(0)), + SNPVersion: toPtr(SVN(8)), + MicrocodeVersion: toPtr(SVN(115)), + }, + } + return mnfst +} + func toPtr[T any](t T) *T { return &t } diff --git a/justfile b/justfile index 7e229472f7..1dd2c6913f 100644 --- a/justfile +++ b/justfile @@ -79,6 +79,7 @@ generate cli=default_cli: nix run .#{{ cli }} -- generate \ --workspace-dir ./{{ workspace_dir }} \ --image-replacements ./{{ workspace_dir }}/just.containerlookup \ + --reference-values aks \ ./{{ workspace_dir }}/deployment/*.yml duration=$(( $(date +%s) - $t )) echo "Generated policies in $duration seconds."