Secure boot support: enabling/disabling and resetting keys
Story: #2008270 Task: #41582 Change-Id: I72f8103fe9bb4613ef8c53a3ff08a710dc96c12d
This commit is contained in:
parent
e4a3e18de0
commit
5425c005e2
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for UEFI secure boot: reading the current status, enabling or
|
||||
disabling secure boot, resetting keys.
|
|
@ -25,7 +25,9 @@ class SushyError(Exception):
|
|||
|
||||
message = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, message=None, **kwargs):
|
||||
if message is not None:
|
||||
self.message = message
|
||||
if self.message and kwargs:
|
||||
self.message = self.message % kwargs
|
||||
|
||||
|
|
|
@ -173,3 +173,20 @@ SYSTEM_TYPE_VIRTUALLY_PARTITIONED = "VirtuallyPartitioned"
|
|||
"""A virtual or software-based partition of a computer system"""
|
||||
SYSTEM_TYPE_COMPOSED = "Composed"
|
||||
"""A computer system created by binding resource blocks together"""
|
||||
|
||||
# Secure boot constants
|
||||
|
||||
SECURE_BOOT_ENABLED = "Enabled"
|
||||
"""UEFI secure boot is enabled."""
|
||||
|
||||
SECURE_BOOT_DISABLED = "Disabled"
|
||||
"""UEFI secure boot is disabled."""
|
||||
|
||||
SECURE_BOOT_MODE_SETUP = "SetupMode"
|
||||
SECURE_BOOT_MODE_USER = "UserMode"
|
||||
SECURE_BOOT_MODE_AUDIT = "AuditMode"
|
||||
SECURE_BOOT_MODE_DEPLOYED = "DeployedMode"
|
||||
|
||||
SECURE_BOOT_RESET_KEYS_TO_DEFAULT = "ResetAllKeysToDefault"
|
||||
SECURE_BOOT_RESET_KEYS_DELETE_ALL = "DeleteAllKeys"
|
||||
SECURE_BOOT_RESET_KEYS_DELETE_PK = "DeletePK"
|
||||
|
|
|
@ -113,3 +113,27 @@ SYSTEM_TYPE_VALUE_MAP = {
|
|||
|
||||
SYSTEM_TYPE_VALUE_MAP_REV = (
|
||||
utils.revert_dictionary(SYSTEM_TYPE_VALUE_MAP))
|
||||
|
||||
SECURE_BOOT_STATE = {
|
||||
'Enabled': sys_cons.SECURE_BOOT_ENABLED,
|
||||
'Disabled': sys_cons.SECURE_BOOT_DISABLED,
|
||||
}
|
||||
|
||||
SECURE_BOOT_STATE_REV = utils.revert_dictionary(SECURE_BOOT_STATE)
|
||||
|
||||
SECURE_BOOT_MODE = {
|
||||
'SetupMode': sys_cons.SECURE_BOOT_MODE_SETUP,
|
||||
'UserMode': sys_cons.SECURE_BOOT_MODE_USER,
|
||||
'AuditMode': sys_cons.SECURE_BOOT_MODE_AUDIT,
|
||||
'DeployedMode': sys_cons.SECURE_BOOT_MODE_DEPLOYED,
|
||||
}
|
||||
|
||||
SECURE_BOOT_MODE_REV = utils.revert_dictionary(SECURE_BOOT_MODE)
|
||||
|
||||
SECURE_BOOT_RESET_KEYS = {
|
||||
'ResetAllKeysToDefault': sys_cons.SECURE_BOOT_RESET_KEYS_TO_DEFAULT,
|
||||
'DeleteAllKeys': sys_cons.SECURE_BOOT_RESET_KEYS_DELETE_ALL,
|
||||
'DeletePK': sys_cons.SECURE_BOOT_RESET_KEYS_DELETE_PK,
|
||||
}
|
||||
|
||||
SECURE_BOOT_RESET_KEYS_REV = utils.revert_dictionary(SECURE_BOOT_RESET_KEYS)
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# This is referred from Redfish standard schema.
|
||||
# http://redfish.dmtf.org/schemas/v1/SecureBoot.v1_1_0.json
|
||||
|
||||
import logging
|
||||
|
||||
from sushy import exceptions
|
||||
from sushy.resources import base
|
||||
from sushy.resources import common
|
||||
from sushy.resources.system import mappings
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResetKeysActionField(common.ActionField):
|
||||
|
||||
allowed_values = base.Field('ResetKeysType@Redfish.AllowableValues',
|
||||
adapter=list)
|
||||
|
||||
|
||||
class ActionsField(base.CompositeField):
|
||||
|
||||
reset_keys = ResetKeysActionField('#SecureBoot.ResetKeys')
|
||||
"""Action that resets the UEFI Secure Boot keys."""
|
||||
|
||||
|
||||
class SecureBoot(base.ResourceBase):
|
||||
|
||||
identity = base.Field('Id', required=True)
|
||||
"""The Bios resource identity string"""
|
||||
|
||||
name = base.Field('Name')
|
||||
"""The name of the resource"""
|
||||
|
||||
description = base.Field('Description')
|
||||
"""Human-readable description of the BIOS resource"""
|
||||
|
||||
current_boot = base.MappedField('SecureBootCurrentBoot',
|
||||
mappings.SECURE_BOOT_STATE)
|
||||
"""The UEFI Secure Boot state during the current boot cycle."""
|
||||
|
||||
enabled = base.Field('SecureBootEnable')
|
||||
"""Whether the UEFI Secure Boot takes effect on next boot.
|
||||
|
||||
This property can be enabled in UEFI boot mode only.
|
||||
"""
|
||||
|
||||
mode = base.MappedField('SecureBootMode', mappings.SECURE_BOOT_MODE)
|
||||
"""The current UEFI Secure Boot Mode."""
|
||||
|
||||
# TODO(dtantsur): SecureBootDatabases
|
||||
|
||||
_actions = ActionsField('Actions')
|
||||
|
||||
def __init__(self, connector, path, redfish_version=None, registries=None):
|
||||
"""A class representing secure boot settings.
|
||||
|
||||
:param connector: A Connector instance
|
||||
:param path: Sub-URI path to the SecureBoot resource
|
||||
:param registries: Dict of message registries to be used when
|
||||
parsing messages of attribute update status
|
||||
"""
|
||||
super().__init__(connector, path, redfish_version, registries)
|
||||
|
||||
def _get_reset_action_element(self):
|
||||
reset_action = self._actions.reset_keys
|
||||
if not reset_action:
|
||||
raise exceptions.MissingActionError(action='#SecureBoot.ResetKeys',
|
||||
resource=self._path)
|
||||
return reset_action
|
||||
|
||||
def get_allowed_reset_keys_values(self):
|
||||
"""Get the allowed values for resetting the keys.
|
||||
|
||||
:returns: A set with the allowed values.
|
||||
"""
|
||||
reset_action = self._get_reset_action_element()
|
||||
|
||||
if not reset_action.allowed_values:
|
||||
LOG.warning('Could not figure out the allowed values for the '
|
||||
'reset keys action for %s', self.identity)
|
||||
return set(mappings.SECURE_BOOT_RESET_KEYS_REV)
|
||||
|
||||
return set([mappings.SECURE_BOOT_RESET_KEYS[v] for v in
|
||||
set(mappings.SECURE_BOOT_RESET_KEYS).
|
||||
intersection(reset_action.allowed_values)])
|
||||
|
||||
def reset_keys(self, reset_type):
|
||||
"""Reset secure boot keys.
|
||||
|
||||
:param reset_type: Reset type, one of `SECORE_BOOT_RESET_KEYS_*`
|
||||
constants.
|
||||
"""
|
||||
valid_resets = self.get_allowed_reset_keys_values()
|
||||
if reset_type not in valid_resets:
|
||||
raise exceptions.InvalidParameterValueError(
|
||||
parameter='reset_type', value=reset_type,
|
||||
valid_values=valid_resets)
|
||||
|
||||
target_uri = self._get_reset_action_element().target_uri
|
||||
self._conn.post(target_uri, data={'ResetKeysType': reset_type})
|
||||
|
||||
def set_enabled(self, enabled):
|
||||
"""Enable/disable secure boot.
|
||||
|
||||
:param enabled: True, if secure boot is enabled for next boot.
|
||||
"""
|
||||
if not isinstance(enabled, bool):
|
||||
raise exceptions.InvalidParameterValueError(
|
||||
"Expected a boolean for 'enabled', got %r" % enabled)
|
||||
|
||||
self._conn.patch(self.path, data={'SecureBootEnable': enabled})
|
|
@ -31,6 +31,7 @@ from sushy.resources.system import constants as sys_cons
|
|||
from sushy.resources.system import ethernet_interface
|
||||
from sushy.resources.system import mappings as sys_maps
|
||||
from sushy.resources.system import processor
|
||||
from sushy.resources.system import secure_boot
|
||||
from sushy.resources.system import simple_storage as sys_simple_storage
|
||||
from sushy.resources.system.storage import storage as sys_storage
|
||||
from sushy import utils
|
||||
|
@ -436,6 +437,21 @@ class System(base.ResourceBase):
|
|||
registries=self.registries)
|
||||
for path in paths]
|
||||
|
||||
@property
|
||||
@utils.cache_it
|
||||
def secure_boot(self):
|
||||
"""Property to reference `SecureBoot` instance
|
||||
|
||||
It is set once when the first time it is queried. On refresh,
|
||||
this property is marked as stale (greedy-refresh not done).
|
||||
Here the actual refresh of the sub-resource happens, if stale.
|
||||
"""
|
||||
return secure_boot.SecureBoot(
|
||||
self._conn,
|
||||
utils.get_sub_resource_path_by(self, 'SecureBoot'),
|
||||
redfish_version=self.redfish_version,
|
||||
registries=self.registries)
|
||||
|
||||
|
||||
class SystemCollection(base.ResourceCollectionBase):
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot",
|
||||
"@odata.type": "#SecureBoot.v1_1_0.SecureBoot",
|
||||
"Id": "SecureBoot",
|
||||
"Name": "UEFI Secure Boot",
|
||||
"Actions": {
|
||||
"#SecureBoot.ResetKeys": {
|
||||
"target": "/redfish/v1/Systems/437XR1138R2/SecureBoot/Actions/SecureBoot.ResetKeys",
|
||||
"ResetKeysType@Redfish.AllowableValues": [
|
||||
"ResetAllKeysToDefault",
|
||||
"DeleteAllKeys",
|
||||
"DeletePK"
|
||||
]
|
||||
}
|
||||
} ,
|
||||
"SecureBootEnable": false,
|
||||
"SecureBootCurrentBoot": "Disabled",
|
||||
"SecureBootMode": "DeployedMode",
|
||||
"SecureBootDatabases": {
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
from sushy import exceptions
|
||||
from sushy.resources.system import constants
|
||||
from sushy.resources.system import secure_boot
|
||||
from sushy.tests.unit import base
|
||||
|
||||
|
||||
class SecureBootTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SecureBootTestCase, self).setUp()
|
||||
self.conn = mock.Mock()
|
||||
with open('sushy/tests/unit/json_samples/secure_boot.json') as f:
|
||||
self.secure_boot_json = json.load(f)
|
||||
|
||||
self.conn.get.return_value.json.side_effect = [
|
||||
self.secure_boot_json
|
||||
]
|
||||
self.secure_boot = secure_boot.SecureBoot(
|
||||
self.conn, '/redfish/v1/Systems/437XR1138R2/SecureBoot',
|
||||
registries={}, redfish_version='1.1.0')
|
||||
|
||||
def test__parse_attributes(self):
|
||||
self.secure_boot._parse_attributes(self.secure_boot_json)
|
||||
self.assertEqual('1.1.0', self.secure_boot.redfish_version)
|
||||
self.assertEqual('SecureBoot', self.secure_boot.identity)
|
||||
self.assertEqual('UEFI Secure Boot', self.secure_boot.name)
|
||||
self.assertIsNone(self.secure_boot.description)
|
||||
self.assertIs(False, self.secure_boot.enabled)
|
||||
self.assertEqual(constants.SECURE_BOOT_DISABLED,
|
||||
self.secure_boot.current_boot)
|
||||
self.assertEqual(constants.SECURE_BOOT_MODE_DEPLOYED,
|
||||
self.secure_boot.mode)
|
||||
|
||||
@mock.patch.object(secure_boot.LOG, 'warning', autospec=True)
|
||||
def test_get_allowed_reset_keys_values(self, mock_log):
|
||||
self.assertEqual({constants.SECURE_BOOT_RESET_KEYS_TO_DEFAULT,
|
||||
constants.SECURE_BOOT_RESET_KEYS_DELETE_ALL,
|
||||
constants.SECURE_BOOT_RESET_KEYS_DELETE_PK},
|
||||
self.secure_boot.get_allowed_reset_keys_values())
|
||||
self.assertFalse(mock_log.called)
|
||||
|
||||
@mock.patch.object(secure_boot.LOG, 'warning', autospec=True)
|
||||
def test_get_allowed_reset_keys_values_no_values(self, mock_log):
|
||||
self.secure_boot._actions.reset_keys.allowed_values = None
|
||||
self.assertEqual({constants.SECURE_BOOT_RESET_KEYS_TO_DEFAULT,
|
||||
constants.SECURE_BOOT_RESET_KEYS_DELETE_ALL,
|
||||
constants.SECURE_BOOT_RESET_KEYS_DELETE_PK},
|
||||
self.secure_boot.get_allowed_reset_keys_values())
|
||||
self.assertTrue(mock_log.called)
|
||||
|
||||
@mock.patch.object(secure_boot.LOG, 'warning', autospec=True)
|
||||
def test_get_allowed_reset_keys_values_custom_values(self, mock_log):
|
||||
self.secure_boot._actions.reset_keys.allowed_values = [
|
||||
'ResetAllKeysToDefault',
|
||||
'IamNotRedfishCompatible',
|
||||
]
|
||||
self.assertEqual({constants.SECURE_BOOT_RESET_KEYS_TO_DEFAULT},
|
||||
self.secure_boot.get_allowed_reset_keys_values())
|
||||
self.assertFalse(mock_log.called)
|
||||
|
||||
def test_set_enabled(self):
|
||||
self.secure_boot.set_enabled(True)
|
||||
self.conn.patch.assert_called_once_with(
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot',
|
||||
data={'SecureBootEnable': True})
|
||||
|
||||
def test_set_enabled_wrong_type(self):
|
||||
self.assertRaises(exceptions.InvalidParameterValueError,
|
||||
self.secure_boot.set_enabled, 'banana')
|
||||
|
||||
def test_reset_keys(self):
|
||||
self.secure_boot.reset_keys(
|
||||
constants.SECURE_BOOT_RESET_KEYS_TO_DEFAULT)
|
||||
self.conn.post.assert_called_once_with(
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot'
|
||||
'/Actions/SecureBoot.ResetKeys',
|
||||
data={'ResetKeysType': 'ResetAllKeysToDefault'})
|
||||
|
||||
def test_reset_keys_wrong_value(self):
|
||||
self.assertRaises(exceptions.InvalidParameterValueError,
|
||||
self.secure_boot.reset_keys, 'DeleteEverything')
|
|
@ -27,6 +27,7 @@ from sushy.resources.oem import fake
|
|||
from sushy.resources.system import bios
|
||||
from sushy.resources.system import mappings as sys_map
|
||||
from sushy.resources.system import processor
|
||||
from sushy.resources.system import secure_boot
|
||||
from sushy.resources.system import simple_storage
|
||||
from sushy.resources.system import system
|
||||
from sushy.tests.unit import base
|
||||
|
@ -520,6 +521,15 @@ class SystemTestCase(base.TestCase):
|
|||
self.assertEqual('BIOS Configuration Current Settings',
|
||||
self.sys_inst.bios.name)
|
||||
|
||||
def test_secure_boot(self):
|
||||
self.conn.get.return_value.json.reset_mock()
|
||||
with open('sushy/tests/unit/json_samples/secure_boot.json') as f:
|
||||
self.conn.get.return_value.json.side_effect = [json.load(f)]
|
||||
|
||||
self.assertIsInstance(self.sys_inst.secure_boot,
|
||||
secure_boot.SecureBoot)
|
||||
self.assertEqual('UEFI Secure Boot', self.sys_inst.secure_boot.name)
|
||||
|
||||
def test_simple_storage_for_missing_attr(self):
|
||||
self.sys_inst.json.pop('SimpleStorage')
|
||||
with self.assertRaisesRegex(
|
||||
|
|
Loading…
Reference in New Issue