Skip to content

Commit 7e171e8

Browse files
authored
Merge pull request #341 from andyroyle/fix/empty-shielded-initial-state-overrides-source
fix: avoid sending empty ShieldedInstanceInitialState on image create
2 parents eb54ee8 + da99f65 commit 7e171e8

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

builder/googlecompute/step_create_image_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,32 @@ func TestStepCreateImage_setsDeprecationFields(t *testing.T) {
9494
assert.Contains(t, []string{"DEPRECATED", "ACTIVE"}, d.DeprecatedImageStatus.State, "State should be DEPRECATED or ACTIVE")
9595
}
9696

97+
// Regression test: when no Secure Boot signature inputs are configured, the
98+
// image insert request must not include a shieldedInstanceInitialState field.
99+
// Sending an explicit (even empty) InitialStateConfig replaces the
100+
// PK/KEKs/db/dbx that would otherwise be inherited from the source disk and
101+
// causes Secure Boot to fail on VMs launched from the resulting image
102+
// (UEFI: "Status: Security Violation").
103+
func TestStepCreateImage_noSignatureInputsLeavesInitialStateNil(t *testing.T) {
104+
state := testState(t)
105+
step := new(StepCreateImage)
106+
defer step.Cleanup(state)
107+
108+
c := state.Get("config").(*Config)
109+
d := state.Get("driver").(*common.DriverMock)
110+
111+
// Clear all signature inputs supplied by the default testConfig.
112+
c.ImagePlatformKey = ""
113+
c.ImageKeyExchangeKey = nil
114+
c.ImageSignaturesDB = nil
115+
c.ImageForbiddenSignaturesDB = nil
116+
117+
action := step.Run(context.Background(), state)
118+
assert.Equal(t, multistep.ActionContinue, action, "Step did not pass.")
119+
120+
assert.Nil(t, d.CreateImageSpec.ShieldedInstanceInitialState, "shieldedInstanceInitialState must be nil so the source disk's initial state is inherited")
121+
}
122+
97123
func TestStepCreateImageNonUEFI_image(t *testing.T) {
98124
state := testState(t)
99125
step := new(StepCreateImage)

lib/common/shielded_vms.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ func FillFileContentBuffer(certOrKeyFile string) (*compute.FileContentBuffer, er
3636
}
3737

3838
func CreateShieldedVMStateConfig(imagePlatformKey string, imageKeyExchangeKey []string, imageSignaturesDB []string, imageForbiddenSignaturesDB []string) (*compute.InitialStateConfig, error) {
39+
// When no Secure Boot signature inputs are configured, return nil so the
40+
// caller leaves ShieldedInstanceInitialState unset on the image payload.
41+
// Sending an explicit (even empty) InitialStateConfig replaces the
42+
// PK/KEKs/db/dbx that would otherwise be inherited from the source disk,
43+
// which causes Secure Boot to fail on VMs launched from the resulting
44+
// image (UEFI: "Status: Security Violation").
45+
if imagePlatformKey == "" && len(imageKeyExchangeKey) == 0 && len(imageSignaturesDB) == 0 && len(imageForbiddenSignaturesDB) == 0 {
46+
return nil, nil
47+
}
48+
3949
shieldedVMStateConfig := &compute.InitialStateConfig{}
4050
if imagePlatformKey != "" {
4151
shieldedData, err := FillFileContentBuffer(imagePlatformKey)

lib/common/shielded_vms_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright IBM Corp. 2013, 2025
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package common
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
// When no Secure Boot signature inputs are configured, the helper must return
15+
// a nil *InitialStateConfig so the caller does not attach an empty
16+
// shieldedInstanceInitialState to the image insert request. Attaching an
17+
// empty value overrides the PK/KEKs/db/dbx that would otherwise be inherited
18+
// from the source disk and breaks Secure Boot on the resulting image.
19+
func TestCreateShieldedVMStateConfig_NoInputsReturnsNil(t *testing.T) {
20+
cfg, err := CreateShieldedVMStateConfig("", nil, nil, nil)
21+
assert.NoError(t, err)
22+
assert.Nil(t, cfg, "expected nil config when no signature inputs are configured")
23+
24+
cfg, err = CreateShieldedVMStateConfig("", []string{}, []string{}, []string{})
25+
assert.NoError(t, err)
26+
assert.Nil(t, cfg, "expected nil config when signature inputs are empty slices")
27+
}
28+
29+
func TestCreateShieldedVMStateConfig_PopulatesFieldsWhenInputsProvided(t *testing.T) {
30+
dir := t.TempDir()
31+
keyPath := filepath.Join(dir, "fake-key")
32+
if err := os.WriteFile(keyPath, []byte("fake key data"), 0600); err != nil {
33+
t.Fatalf("failed to write fake key: %v", err)
34+
}
35+
36+
testcases := []struct {
37+
name string
38+
pk string
39+
keks []string
40+
dbs []string
41+
dbxs []string
42+
}{
43+
{name: "platform key only", pk: keyPath},
44+
{name: "kek only", keks: []string{keyPath}},
45+
{name: "db only", dbs: []string{keyPath}},
46+
{name: "dbx only", dbxs: []string{keyPath}},
47+
}
48+
49+
for _, tt := range testcases {
50+
t.Run(tt.name, func(t *testing.T) {
51+
cfg, err := CreateShieldedVMStateConfig(tt.pk, tt.keks, tt.dbs, tt.dbxs)
52+
assert.NoError(t, err)
53+
assert.NotNil(t, cfg)
54+
55+
assert.Equal(t, tt.pk != "", cfg.Pk != nil, "Pk presence should match input")
56+
assert.Len(t, cfg.Keks, len(tt.keks))
57+
assert.Len(t, cfg.Dbs, len(tt.dbs))
58+
assert.Len(t, cfg.Dbxs, len(tt.dbxs))
59+
})
60+
}
61+
}

0 commit comments

Comments
 (0)