Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,70 @@
},
"response": []
},
{
"name": "Send Advanced Power Action - EnforceSecureBoot false in CCM",
"event": [
{
"listen": "test",
"script": {
"exec": [
"// This test validates the CCM restriction for EnforceSecureBoot\r",
"// When a device is in Client Control Mode (CCM), EnforceSecureBoot cannot be turned off\r",
"// Expected: 400 Bad Request with error message about CCM restriction\r",
"// Note: This test requires a device in CCM mode to properly validate\r",
"\r",
"pm.test(\"Status code is 400 or 404\", function () {\r",
" // 400 = CCM restriction error (expected for CCM device)\r",
" // 404 = Device not found (if no device configured)\r",
" pm.expect(pm.response.code).to.be.oneOf([400, 404]);\r",
"});\r",
"\r",
"pm.test(\"Response contains appropriate error message\", function () {\r",
" var jsonData = pm.response.json();\r",
" if (pm.response.code === 400) {\r",
" pm.expect(jsonData.error).to.include(\"EnforceSecureBoot\");\r",
" pm.expect(jsonData.error).to.include(\"CCM\");\r",
" } else if (pm.response.code === 404) {\r",
" pm.expect(jsonData.error).to.eq(\"Error not found\");\r",
" }\r",
"});\r",
""
],
"type": "text/javascript",
"packages": {}
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"action\": 105,\r\n \"useSOL\": false,\r\n \"bootDetails\": {\r\n \"url\": \"https://example.com/boot.efi\",\r\n \"enforceSecureBoot\": false\r\n }\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{protocol}}://{{host}}/api/v1/amt/power/bootoptions/{{deviceId}}",
"protocol": "{{protocol}}",
"host": [
"{{host}}"
],
"path": [
"api",
"v1",
"amt",
"power",
"bootoptions",
"{{deviceId}}"
]
}
},
"response": []
},
{
"name": "Get AMT Features",
"event": [
Expand Down
4 changes: 4 additions & 0 deletions internal/controller/httpapi/v1/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func ErrorResponse(c *gin.Context, err error) {
NotUniqueErr sqldb.NotUniqueError
amtErr devices.AMTError
notSupportedErr devices.NotSupportedError
validationErr devices.ValidationError
certExpErr domains.CertExpirationError
certPasswordErr domains.CertPasswordError
netErr net.Error
Expand All @@ -49,6 +50,9 @@ func ErrorResponse(c *gin.Context, err error) {
dbErrorHandle(c, dbErr)
case errors.As(err, &amtErr):
amtErrorHandle(c, amtErr)
case errors.As(err, &validationErr):
msg := validationErr.Console.FriendlyMessage()
c.AbortWithStatusJSON(http.StatusBadRequest, response{Error: msg, Message: msg})
case errors.As(err, &notSupportedErr):
msg := notSupportedErr.Console.FriendlyMessage()
c.AbortWithStatusJSON(http.StatusNotImplemented, response{Error: msg, Message: msg})
Expand Down
2 changes: 1 addition & 1 deletion internal/entity/dto/v1/bootsetting.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type BootDetails struct {
Username string `json:"username" example:"admin"`
Password string `json:"password" example:"password"`
BootPath string `json:"bootPath" example:"\\OemPba.efi"`
EnforceSecureBoot bool `json:"enforceSecureBoot" example:"true"`
EnforceSecureBoot *bool `json:"enforceSecureBoot,omitempty" example:"true"`
}

type BootSetting struct {
Expand Down
37 changes: 33 additions & 4 deletions internal/usecase/devices/power.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/amt/boot"
"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/amt/setupandconfiguration"
cimBoot "github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/boot"
"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/power"
"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/software"
Expand Down Expand Up @@ -38,8 +39,9 @@ const (
)

var (
ErrValidationUseCase = ValidationError{Console: consoleerrors.CreateConsoleError("parameter validation failed")}
ErrLargeFileUseCase = ValidationError{Console: consoleerrors.CreateConsoleError("UEFI file too large")}
ErrValidationUseCase = ValidationError{Console: consoleerrors.CreateConsoleError("parameter validation failed")}
ErrLargeFileUseCase = ValidationError{Console: consoleerrors.CreateConsoleError("UEFI file too large")}
ErrSecureBootCCMRestricted = ValidationError{Console: consoleerrors.CreateConsoleError("EnforceSecureBoot cannot be turned off in CCM")}
)

func (uc *UseCase) SendPowerAction(c context.Context, guid string, action int) (power.PowerActionResponse, error) {
Expand Down Expand Up @@ -244,6 +246,18 @@ func (uc *UseCase) SetBootOptions(c context.Context, guid string, bootSetting dt
return power.PowerActionResponse{}, err
}

// Validate EnforceSecureBoot restriction in CCM
if bootSetting.BootDetails.EnforceSecureBoot != nil && !*bootSetting.BootDetails.EnforceSecureBoot {
setupConfig, err := device.GetSetupAndConfiguration()
if err != nil {
return power.PowerActionResponse{}, err
}

if len(setupConfig) > 0 && setupConfig[0].ProvisioningMode == setupandconfiguration.ClientControlMode {
return power.PowerActionResponse{}, ErrSecureBootCCMRestricted
}
}

bootData, err := device.GetBootData()
if err != nil {
return power.PowerActionResponse{}, err
Expand Down Expand Up @@ -325,7 +339,8 @@ func determineBootDevice(bootSetting dto.BootSetting, newData *boot.BootSettingD
return err
}

setUEFIBootSettings(newData, bootSetting.BootDetails.EnforceSecureBoot, params, typeLengthValueBuffer)
enforceSecureBoot := getEnforceSecureBoot(bootSetting.BootDetails.EnforceSecureBoot, newData.EnforceSecureBoot)
setUEFIBootSettings(newData, enforceSecureBoot, params, typeLengthValueBuffer)
case BootActionPBA, BootActionPowerOnPBA, BootActionWinREBoot, BootActionPowerOnWinREBoot:
if bootSetting.BootDetails.BootPath == "" {
return ErrValidationUseCase
Expand All @@ -336,7 +351,8 @@ func determineBootDevice(bootSetting dto.BootSetting, newData *boot.BootSettingD
return err
}

setUEFIBootSettings(newData, bootSetting.BootDetails.EnforceSecureBoot, params, typeLengthValueBuffer)
enforceSecureBoot := getEnforceSecureBoot(bootSetting.BootDetails.EnforceSecureBoot, newData.EnforceSecureBoot)
setUEFIBootSettings(newData, enforceSecureBoot, params, typeLengthValueBuffer)
case BootActionResetToIDERCDROM, BootActionPowerOnIDERCDROM:
newData.IDERBootDevice = 1
default:
Expand All @@ -346,6 +362,19 @@ func determineBootDevice(bootSetting dto.BootSetting, newData *boot.BootSettingD
return nil
}

// getEnforceSecureBoot returns the EnforceSecureBoot value from the request if provided,
// otherwise falls back to the current device value.
func getEnforceSecureBoot(requestValue *bool, currentValue bool) bool {
if requestValue != nil {
return *requestValue
}

return currentValue
}

// setUEFIBootSettings expects enforceSecureBoot to be a fully resolved value.
// Callers should resolve any optional request value (for example, via getEnforceSecureBoot)
// before invoking this function, as it no longer accepts a *bool.
func setUEFIBootSettings(newData *boot.BootSettingDataRequest, enforceSecureBoot bool, params int, typeLengthValueBuffer []byte) {
newData.BIOSLastStatus = nil
newData.UseIDER = false
Expand Down
200 changes: 200 additions & 0 deletions internal/usecase/devices/power_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
gomock "go.uber.org/mock/gomock"

"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/amt/boot"
"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/amt/setupandconfiguration"
cimBoot "github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/boot"
"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/power"
"github.com/device-management-toolkit/go-wsman-messages/v2/pkg/wsman/cim/service"
Expand Down Expand Up @@ -747,6 +748,205 @@ func TestSetBootOptions(t *testing.T) {
}
}

func TestSetBootOptions_CCMRestriction(t *testing.T) {
t.Parallel()

bootResponse := boot.BootSettingDataResponse{
BIOSLastStatus: []uint16{2, 0},
EnforceSecureBoot: true,
ElementName: "Intel(r) AMT Boot Configuration Settings",
InstanceID: "Intel(r) AMT:BootSettingData 0",
OwningEntity: "Intel(r) AMT",
}

device := &entity.Device{
GUID: "device-guid-123",
TenantID: "tenant-id-456",
}

enforceSecureBootFalse := false
enforceSecureBootTrue := true

powerActionRes := power.PowerActionResponse{ReturnValue: 5}

tests := []struct {
name string
bootSetting dto.BootSetting
manMock func(*mocks.MockWSMAN, *mocks.MockManagement)
repoMock func(*mocks.MockDeviceManagementRepository)
wantErr error
}{
{
name: "CCM restriction - EnforceSecureBoot false in CCM returns error",
bootSetting: dto.BootSetting{
Action: 400,
UseSOL: true,
BootDetails: dto.BootDetails{
EnforceSecureBoot: &enforceSecureBootFalse,
},
},
manMock: func(man *mocks.MockWSMAN, hmm *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(hmm, nil)
hmm.EXPECT().
GetSetupAndConfiguration().
Return([]setupandconfiguration.SetupAndConfigurationServiceResponse{
{ProvisioningMode: setupandconfiguration.ClientControlMode},
}, nil)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
wantErr: devices.ErrSecureBootCCMRestricted,
},
{
name: "ACM mode - EnforceSecureBoot false allowed",
bootSetting: dto.BootSetting{
Action: 400,
UseSOL: true,
BootDetails: dto.BootDetails{
EnforceSecureBoot: &enforceSecureBootFalse,
},
},
manMock: func(man *mocks.MockWSMAN, hmm *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(hmm, nil)
hmm.EXPECT().
GetSetupAndConfiguration().
Return([]setupandconfiguration.SetupAndConfigurationServiceResponse{
{ProvisioningMode: setupandconfiguration.AdminControlMode},
}, nil)
hmm.EXPECT().
GetBootData().
Return(bootResponse, nil)
hmm.EXPECT().
ChangeBootOrder("").
Return(cimBoot.ChangeBootOrder_OUTPUT{}, nil)
hmm.EXPECT().
SetBootData(gomock.Any()).
Return(nil, nil)
hmm.EXPECT().
SetBootConfigRole(1).
Return(powerActionRes, nil)
hmm.EXPECT().
ChangeBootOrder(string(cimBoot.PXE)).
Return(cimBoot.ChangeBootOrder_OUTPUT{}, nil)
hmm.EXPECT().
SendPowerAction(10).
Return(powerActionRes, nil)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
wantErr: nil,
},
{
name: "CCM mode - EnforceSecureBoot true allowed",
bootSetting: dto.BootSetting{
Action: 400,
UseSOL: true,
BootDetails: dto.BootDetails{
EnforceSecureBoot: &enforceSecureBootTrue,
},
},
manMock: func(man *mocks.MockWSMAN, hmm *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(hmm, nil)
hmm.EXPECT().
GetBootData().
Return(bootResponse, nil)
hmm.EXPECT().
ChangeBootOrder("").
Return(cimBoot.ChangeBootOrder_OUTPUT{}, nil)
hmm.EXPECT().
SetBootData(gomock.Any()).
Return(nil, nil)
hmm.EXPECT().
SetBootConfigRole(1).
Return(powerActionRes, nil)
hmm.EXPECT().
ChangeBootOrder(string(cimBoot.PXE)).
Return(cimBoot.ChangeBootOrder_OUTPUT{}, nil)
hmm.EXPECT().
SendPowerAction(10).
Return(powerActionRes, nil)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
wantErr: nil,
},
{
name: "EnforceSecureBoot not provided - no CCM check",
bootSetting: dto.BootSetting{
Action: 400,
UseSOL: true,
BootDetails: dto.BootDetails{
EnforceSecureBoot: nil,
},
},
manMock: func(man *mocks.MockWSMAN, hmm *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(hmm, nil)
hmm.EXPECT().
GetBootData().
Return(bootResponse, nil)
hmm.EXPECT().
ChangeBootOrder("").
Return(cimBoot.ChangeBootOrder_OUTPUT{}, nil)
hmm.EXPECT().
SetBootData(gomock.Any()).
Return(nil, nil)
hmm.EXPECT().
SetBootConfigRole(1).
Return(powerActionRes, nil)
hmm.EXPECT().
ChangeBootOrder(string(cimBoot.PXE)).
Return(cimBoot.ChangeBootOrder_OUTPUT{}, nil)
hmm.EXPECT().
SendPowerAction(10).
Return(powerActionRes, nil)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
wantErr: nil,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

useCase, wsmanMock, management, repo := initPowerTest(t)
tc.manMock(wsmanMock, management)
tc.repoMock(repo)

_, err := useCase.SetBootOptions(context.Background(), device.GUID, tc.bootSetting)

if tc.wantErr != nil {
require.Error(t, err)
require.ErrorIs(t, err, tc.wantErr)
} else {
require.NoError(t, err)
}
})
}
}

func TestGetBootSourceSetting(t *testing.T) {
t.Parallel()

Expand Down
Loading