Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions bmc/secure_boot.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,31 @@ type secureBootCertificateImporterProvider struct {
SecureBootCertificateImporter
}

// CustomSecureBootKeysAllower controls whether the platform will accept
// custom UEFI Secure Boot keys, as opposed to using only the key set the
// firmware shipped with. Vendors expose this differently (a BIOS attribute on
// some platforms, a setup-menu-only setting on others, and not at all on
// platforms that never gate enrollment).
//
// Implementations MUST NOT alter the contents of any Secure Boot key database
// as a side effect. Callers rely on this to sequence a key-store reset and
// this call independently. A platform whose equivalent setting is coupled to
// key-store initialization cannot satisfy this contract and MUST NOT
// implement this interface.
//
// Implementations MUST be idempotent.
//
// rebootRequired reports that the change is staged and will not be in effect
// until the host has been power cycled.
type CustomSecureBootKeysAllower interface {
AllowCustomSecureBootKeys(ctx context.Context, enable bool) (rebootRequired bool, err error)
}

type customSecureBootKeysAllowerProvider struct {
name string
CustomSecureBootKeysAllower
}

func secureBootState(ctx context.Context, generic []secureBootStateGetterProvider) (enabled bool, metadata Metadata, err error) {
metadata = newMetadata()
Loop:
Expand Down Expand Up @@ -224,6 +249,32 @@ Loop:
return metadata, multierror.Append(err, errors.New("failure to import secure boot certificate"))
}

func allowCustomSecureBootKeys(ctx context.Context, generic []customSecureBootKeysAllowerProvider, enable bool) (rebootRequired bool, metadata Metadata, err error) {
metadata = newMetadata()
Loop:
for _, elem := range generic {
if elem.CustomSecureBootKeysAllower == nil {
continue
}
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())
break Loop
default:
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)
rebootRequired, vErr := elem.AllowCustomSecureBootKeys(ctx, enable)
if vErr != nil {
err = multierror.Append(err, errors.WithMessagef(vErr, "provider: %v", elem.name))
continue
}
metadata.SuccessfulProvider = elem.name
return rebootRequired, metadata, nil
}
}

return rebootRequired, metadata, multierror.Append(err, errors.New("failure to set secure boot key management"))
}

// GetSecureBootStateFromInterfaces returns whether UEFI Secure Boot is enabled using
// the first successful SecureBootStateGetter implementation found in generic.
func GetSecureBootStateFromInterfaces(ctx context.Context, generic []interface{}) (enabled bool, metadata Metadata, err error) {
Expand Down Expand Up @@ -380,3 +431,35 @@ func ImportSecureBootCertificateFromInterfaces(ctx context.Context, generic []in

return importSecureBootCertificate(ctx, implementations, database, certificatePEM)
}

// AllowCustomSecureBootKeysFromInterfaces enables/disables acceptance of custom UEFI
// Secure Boot keys using the first successful CustomSecureBootKeysAllower
// implementation found in generic.
func AllowCustomSecureBootKeysFromInterfaces(ctx context.Context, generic []interface{}, enable bool) (rebootRequired bool, metadata Metadata, err error) {
implementations := make([]customSecureBootKeysAllowerProvider, 0)
for _, elem := range generic {
if elem == nil {
continue
}
temp := customSecureBootKeysAllowerProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case CustomSecureBootKeysAllower:
temp.CustomSecureBootKeysAllower = p
implementations = append(implementations, temp)
default:
e := fmt.Sprintf("not a CustomSecureBootKeysAllower implementation: %T", p)
err = multierror.Append(err, errors.New(e))
}
}
if len(implementations) == 0 {
return rebootRequired, metadata, multierror.Append(
err,
errors.Wrap(
bmclibErrs.ErrProviderImplementation,
("no CustomSecureBootKeysAllower implementations found"),
),
)
}

return allowCustomSecureBootKeys(ctx, implementations, enable)
}
57 changes: 57 additions & 0 deletions bmc/secure_boot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ func (m *mockSecureBootCertificateImporter) Name() string {
return "mock"
}

type mockCustomSecureBootKeysAllower struct {
rebootRequired bool
err error
}

func (m *mockCustomSecureBootKeysAllower) AllowCustomSecureBootKeys(ctx context.Context, _ bool) (bool, error) {
return m.rebootRequired, m.err
}

func (m *mockCustomSecureBootKeysAllower) Name() string {
return "mock"
}

func TestGetSecureBootStateFromInterfaces(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -272,3 +285,47 @@ func TestImportSecureBootCertificateFromInterfaces(t *testing.T) {
})
}
}

func TestAllowCustomSecureBootKeysFromInterfaces(t *testing.T) {
testCases := []struct {
name string
generic []interface{}
errMsg string
expectedRebootRequired bool
}{
{
name: "success, reboot required",
generic: []interface{}{&mockCustomSecureBootKeysAllower{rebootRequired: true}},
expectedRebootRequired: true,
},
{
name: "not an implementation",
generic: []interface{}{&mockSecureBootStateGetter{}},
errMsg: "no CustomSecureBootKeysAllower implementations found",
},
{
name: "no implementations",
generic: []interface{}{},
errMsg: "no CustomSecureBootKeysAllower implementations found",
},
{
name: "error from enabler",
generic: []interface{}{&mockCustomSecureBootKeysAllower{err: errors.New("foobar")}},
errMsg: "foobar",
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
rebootRequired, _, err := AllowCustomSecureBootKeysFromInterfaces(context.Background(), tt.generic, true)

if tt.errMsg == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, tt.errMsg)
}

assert.Equal(t, tt.expectedRebootRequired, rebootRequired)
})
}
}
14 changes: 14 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,20 @@ func (c *Client) ImportSecureBootCertificate(ctx context.Context, database bmc.S
return err
}

// AllowCustomSecureBootKeys enables or disables the platform's out-of-band acceptance
// of custom UEFI Secure Boot keys. rebootRequired reports that the change is staged and
// takes effect only after a power cycle.
func (c *Client) AllowCustomSecureBootKeys(ctx context.Context, enable bool) (rebootRequired bool, err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "AllowCustomSecureBootKeys")
defer span.End()

rebootRequired, metadata, err := bmc.AllowCustomSecureBootKeysFromInterfaces(ctx, c.registry().GetDriverInterfaces(), enable)
c.setMetadata(metadata)
metadata.RegisterSpanAttributes(c.Auth.Host, span)

return rebootRequired, err
}

// FirmwareInstall pass through library function to upload firmware and install firmware
func (c *Client) FirmwareInstall(ctx context.Context, component, operationApplyTime string, forceInstall bool, reader io.Reader) (taskID string, err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "FirmwareInstall")
Expand Down
12 changes: 7 additions & 5 deletions providers/dell/idrac.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var (
providers.FeatureResetSecureBootKeys,
providers.FeatureResetSecureBootDatabaseKeys,
providers.FeatureImportSecureBootCertificate,
providers.FeatureAllowCustomSecureBootKeys,
}

errManufacturerUnknown = errors.New("error identifying device manufacturer")
Expand Down Expand Up @@ -108,12 +109,13 @@ func WithUseBasicAuth(useBasicAuth bool) Option {
}
}

// compile-time assertions that the provider implements the BIOS configuration interfaces.
// compile-time assertions that the provider implements these interfaces.
var (
_ bmc.BiosConfigurationGetter = (*Conn)(nil)
_ bmc.BiosConfigurationSetter = (*Conn)(nil)
_ bmc.HTTPBootURISetter = (*Conn)(nil)
_ bmc.NetworkBootEnabledSetter = (*Conn)(nil)
_ bmc.BiosConfigurationGetter = (*Conn)(nil)
_ bmc.BiosConfigurationSetter = (*Conn)(nil)
_ bmc.HTTPBootURISetter = (*Conn)(nil)
_ bmc.NetworkBootEnabledSetter = (*Conn)(nil)
_ bmc.CustomSecureBootKeysAllower = (*Conn)(nil)
)

// Conn details for redfish client
Expand Down
57 changes: 57 additions & 0 deletions providers/dell/secure_boot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dell

import (
"context"

"github.com/pkg/errors"

bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
)

// secureBootPolicyAttribute is Dell's vendor-specific BIOS attribute name and
// MUST NOT leak into any exported bmclib identifier.
const (
secureBootPolicyAttribute = "SecureBootPolicy"
secureBootPolicyCustom = "Custom"
secureBootPolicyStandard = "Standard"
)

// AllowCustomSecureBootKeys sets the SecureBootPolicy BIOS attribute to
// Custom or Standard.
//
// The attribute is read first only to reject platforms that don't expose it at all - not to
// skip the write when the currently *applied* value already matches what's requested.
// Currently-applied state can match while a different value is genuinely *pending* from an
// earlier call in the same boot cycle (e.g. a stale pending PATCH left staged by an interrupted,
// unrelated caller); confirmed live, skipping the write in that case silently left
// SecureBootPolicy staged as Standard despite a request to set it to Custom - the same class of
// bug stmcginnis/gofish#571 fixes one layer down, in SetBiosConfiguration's own diff baseline.
// This early return happens before SetBiosConfiguration is ever called, so #571 can't reach it.
// The write is staged into the
// Bios/Settings resource and only takes effect on the next POST, so a successful call always
// reports rebootRequired true.
//
// Implements bmc.CustomSecureBootKeysAllower.
func (c *Conn) AllowCustomSecureBootKeys(ctx context.Context, enable bool) (rebootRequired bool, err error) {
biosConfig, err := c.redfishwrapper.GetBiosConfiguration(ctx)
if err != nil {
return false, err
}

if _, ok := biosConfig[secureBootPolicyAttribute]; !ok {
return false, bmclibErrs.NewErrUnsupportedHardware(
secureBootPolicyAttribute + " BIOS attribute not present: platform has no out-of-band Secure Boot key management control",
)
}

want := secureBootPolicyStandard
if enable {
want = secureBootPolicyCustom
}

if err := c.redfishwrapper.SetBiosConfiguration(ctx, map[string]string{secureBootPolicyAttribute: want}); err != nil {
return false, errors.Wrapf(err, "failed to set %s", secureBootPolicyAttribute)
}

return true, nil
}
Loading
Loading