Merge "Initial support for secure boot databases"
This commit is contained in:
commit
d4c7e0395b
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for fetching and resetting individual UEFI secure boot
|
||||
databases.
|
@ -190,3 +190,17 @@ 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"
|
||||
|
||||
SECURE_BOOT_PLATFORM_KEY = "PK"
|
||||
SECURE_BOOT_KEY_EXCHANGE_KEYS = "KEK"
|
||||
SECURE_BOOT_ALLOWED_KEYS_DATABASE = "db"
|
||||
SECURE_BOOT_DENIED_KEYS_DATABASE = "dbx"
|
||||
SECURE_BOOT_RECOVERY_KEYS_DATABASE = "dbr"
|
||||
SECURE_BOOT_TIMESTAMP_DATABASE = "dbt"
|
||||
|
||||
SECURE_BOOT_DEFAULT_PLATFORM_KEY = "PKDefault"
|
||||
SECURE_BOOT_DEFAULT_KEY_EXCHANGE_KEYS = "KEKDefault"
|
||||
SECURE_BOOT_DEFAULT_ALLOWED_KEYS_DATABASE = "dbDefault"
|
||||
SECURE_BOOT_DEFAULT_DENIED_KEYS_DATABASE = "dbxDefault"
|
||||
SECURE_BOOT_DEFAULT_RECOVERY_KEYS_DATABASE = "dbrDefault"
|
||||
SECURE_BOOT_DEFAULT_TIMESTAMP_DATABASE = "dbtDefault"
|
||||
|
@ -137,3 +137,29 @@ SECURE_BOOT_RESET_KEYS = {
|
||||
}
|
||||
|
||||
SECURE_BOOT_RESET_KEYS_REV = utils.revert_dictionary(SECURE_BOOT_RESET_KEYS)
|
||||
|
||||
SECURE_BOOT_DATABASE_TYPE = {
|
||||
'PK': sys_cons.SECURE_BOOT_PLATFORM_KEY,
|
||||
'KEK': sys_cons.SECURE_BOOT_KEY_EXCHANGE_KEYS,
|
||||
'db': sys_cons.SECURE_BOOT_ALLOWED_KEYS_DATABASE,
|
||||
'dbx': sys_cons.SECURE_BOOT_DENIED_KEYS_DATABASE,
|
||||
'dbr': sys_cons.SECURE_BOOT_RECOVERY_KEYS_DATABASE,
|
||||
'dbt': sys_cons.SECURE_BOOT_TIMESTAMP_DATABASE,
|
||||
'PKDefault': sys_cons.SECURE_BOOT_DEFAULT_PLATFORM_KEY,
|
||||
'KEKDefault': sys_cons.SECURE_BOOT_DEFAULT_KEY_EXCHANGE_KEYS,
|
||||
'dbDefault': sys_cons.SECURE_BOOT_DEFAULT_ALLOWED_KEYS_DATABASE,
|
||||
'dbxDefault': sys_cons.SECURE_BOOT_DEFAULT_DENIED_KEYS_DATABASE,
|
||||
'dbrDefault': sys_cons.SECURE_BOOT_DEFAULT_RECOVERY_KEYS_DATABASE,
|
||||
'dbtDefault': sys_cons.SECURE_BOOT_DEFAULT_TIMESTAMP_DATABASE,
|
||||
}
|
||||
|
||||
SECURE_BOOT_DATABASE_TYPE_REV = utils.revert_dictionary(
|
||||
SECURE_BOOT_DATABASE_TYPE)
|
||||
|
||||
SECURE_BOOT_DATABASE_RESET_KEYS = {
|
||||
'ResetAllKeysToDefault': sys_cons.SECURE_BOOT_RESET_KEYS_TO_DEFAULT,
|
||||
'DeleteAllKeys': sys_cons.SECURE_BOOT_RESET_KEYS_DELETE_ALL,
|
||||
}
|
||||
|
||||
SECURE_BOOT_DATABASE_RESET_KEYS_REV = utils.revert_dictionary(
|
||||
SECURE_BOOT_DATABASE_RESET_KEYS)
|
||||
|
@ -19,6 +19,8 @@ from sushy import exceptions
|
||||
from sushy.resources import base
|
||||
from sushy.resources import common
|
||||
from sushy.resources.system import mappings
|
||||
from sushy.resources.system import secure_boot_database
|
||||
from sushy import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -73,6 +75,25 @@ class SecureBoot(base.ResourceBase):
|
||||
"""
|
||||
super().__init__(connector, path, redfish_version, registries)
|
||||
|
||||
@property
|
||||
@utils.cache_it
|
||||
def databases(self):
|
||||
"""A collection of secure boot databases.
|
||||
|
||||
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.
|
||||
|
||||
:raises: MissingAttributeError if 'SecureBootDatabases/@odata.id' field
|
||||
is missing.
|
||||
:returns: `SimpleStorageCollection` instance
|
||||
"""
|
||||
return secure_boot_database.SecureBootDatabaseCollection(
|
||||
self._conn, utils.get_sub_resource_path_by(
|
||||
self, "SecureBootDatabases"),
|
||||
redfish_version=self.redfish_version,
|
||||
registries=self.registries)
|
||||
|
||||
def _get_reset_action_element(self):
|
||||
reset_action = self._actions.reset_keys
|
||||
if not reset_action:
|
||||
|
112
sushy/resources/system/secure_boot_database.py
Normal file
112
sushy/resources/system/secure_boot_database.py
Normal file
@ -0,0 +1,112 @@
|
||||
# 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 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('#SecureBootDatabase.ResetKeys')
|
||||
"""Action that resets the UEFI Secure Boot keys."""
|
||||
|
||||
|
||||
class SecureBootDatabase(base.ResourceBase):
|
||||
|
||||
# TODO(dtantsur): certificates
|
||||
|
||||
database_id = base.MappedField('DatabaseId',
|
||||
mappings.SECURE_BOOT_DATABASE_TYPE)
|
||||
"""Standard UEFI database type."""
|
||||
|
||||
description = base.Field('Description')
|
||||
"""The system description"""
|
||||
|
||||
identity = base.Field('Id', required=True)
|
||||
"""The secure boot database identity string"""
|
||||
|
||||
name = base.Field('Name')
|
||||
"""The secure boot database name"""
|
||||
|
||||
# TODO(dtantsur): signatures
|
||||
|
||||
_actions = ActionsField('Actions')
|
||||
|
||||
def _get_reset_action_element(self):
|
||||
reset_action = self._actions.reset_keys
|
||||
if not reset_action:
|
||||
raise exceptions.MissingActionError(
|
||||
action='#SecureBootDatabase.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_DATABASE_RESET_KEYS_REV)
|
||||
|
||||
return set([mappings.SECURE_BOOT_DATABASE_RESET_KEYS[v] for v in
|
||||
set(mappings.SECURE_BOOT_DATABASE_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})
|
||||
|
||||
|
||||
class SecureBootDatabaseCollection(base.ResourceCollectionBase):
|
||||
|
||||
@property
|
||||
def _resource_type(self):
|
||||
return SecureBootDatabase
|
||||
|
||||
def __init__(self, connector, path, redfish_version=None, registries=None):
|
||||
"""A class representing a ComputerSystemCollection
|
||||
|
||||
:param connector: A Connector instance
|
||||
:param path: The canonical path to the System collection resource
|
||||
:param redfish_version: The version of RedFish. Used to construct
|
||||
the object according to schema of the given version.
|
||||
:param registries: Dict of Redfish Message Registry objects to be
|
||||
used in any resource that needs registries to parse messages
|
||||
"""
|
||||
super(SecureBootDatabaseCollection, self).__init__(
|
||||
connector, path, redfish_version, registries)
|
26
sushy/tests/unit/json_samples/secure_boot_database.json
Normal file
26
sushy/tests/unit/json_samples/secure_boot_database.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"@odata.type": "#SecureBootDatabase.v1_0_0.SecureBootDatabase",
|
||||
"Id": "db",
|
||||
"Name": "db - Authorized Signature Database",
|
||||
"Description": "UEFI db Secure Boot Database",
|
||||
"DatabaseId": "db",
|
||||
"Certificates": {
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/db/Certificates/"
|
||||
},
|
||||
"Signatures": {
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/db/Signatures/"
|
||||
},
|
||||
"Actions": {
|
||||
"#SecureBootDatabase.ResetKeys": {
|
||||
"target": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/db/Actions/SecureBootDatabase.ResetKeys",
|
||||
"ResetKeysType@Redfish.AllowableValues": [
|
||||
"ResetAllKeysToDefault",
|
||||
"DeleteAllKeys"
|
||||
]
|
||||
},
|
||||
"Oem": {}
|
||||
},
|
||||
"Oem": {},
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/db",
|
||||
"@Redfish.Copyright": "Copyright 2014-2021 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"@odata.type": "#SecureBootDatabaseCollection.SecureBootDatabaseCollection",
|
||||
"Name": "UEFI SecureBoot Database Collection",
|
||||
"Members@odata.count": 8,
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/PK"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/KEK"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/db"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/dbx"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/PKDefault"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/KEKDefault"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/dbDefault"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/dbxDefault"
|
||||
}
|
||||
],
|
||||
"Oem": {},
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases",
|
||||
"@Redfish.Copyright": "Copyright 2014-2021 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
@ -16,6 +16,7 @@ from unittest import mock
|
||||
from sushy import exceptions
|
||||
from sushy.resources.system import constants
|
||||
from sushy.resources.system import secure_boot
|
||||
from sushy.resources.system import secure_boot_database
|
||||
from sushy.tests.unit import base
|
||||
|
||||
|
||||
@ -27,9 +28,7 @@ class SecureBootTestCase(base.TestCase):
|
||||
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.conn.get.return_value.json.return_value = self.secure_boot_json
|
||||
self.secure_boot = secure_boot.SecureBoot(
|
||||
self.conn, '/redfish/v1/Systems/437XR1138R2/SecureBoot',
|
||||
registries={}, redfish_version='1.1.0')
|
||||
@ -94,3 +93,20 @@ class SecureBootTestCase(base.TestCase):
|
||||
def test_reset_keys_wrong_value(self):
|
||||
self.assertRaises(exceptions.InvalidParameterValueError,
|
||||
self.secure_boot.reset_keys, 'DeleteEverything')
|
||||
|
||||
def test_databases(self):
|
||||
self.conn.get.return_value.json.reset_mock()
|
||||
|
||||
with open('sushy/tests/unit/json_samples/'
|
||||
'secure_boot_database_collection.json') as f:
|
||||
self.conn.get.return_value.json.return_value = json.load(f)
|
||||
|
||||
result = self.secure_boot.databases
|
||||
self.assertIsInstance(
|
||||
result, secure_boot_database.SecureBootDatabaseCollection)
|
||||
self.conn.get.return_value.json.assert_called_once_with()
|
||||
|
||||
self.conn.get.return_value.json.reset_mock()
|
||||
|
||||
self.assertIs(result, self.secure_boot.databases)
|
||||
self.conn.get.return_value.json.assert_not_called()
|
||||
|
138
sushy/tests/unit/resources/system/test_secure_boot_database.py
Normal file
138
sushy/tests/unit/resources/system/test_secure_boot_database.py
Normal file
@ -0,0 +1,138 @@
|
||||
# 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_database
|
||||
from sushy.tests.unit import base
|
||||
|
||||
|
||||
class SecureBootDatabaseTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SecureBootDatabaseTestCase, self).setUp()
|
||||
self.conn = mock.Mock()
|
||||
with open('sushy/tests/unit/json_samples/'
|
||||
'secure_boot_database.json') as f:
|
||||
self.secure_boot_json = json.load(f)
|
||||
|
||||
self.conn.get.return_value.json.return_value = self.secure_boot_json
|
||||
self.secure_boot = secure_boot_database.SecureBootDatabase(
|
||||
self.conn,
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot'
|
||||
'/SecureBootDatabases/db',
|
||||
registries={}, redfish_version='1.0.0')
|
||||
|
||||
def test__parse_attributes(self):
|
||||
self.secure_boot._parse_attributes(self.secure_boot_json)
|
||||
self.assertEqual('1.0.0', self.secure_boot.redfish_version)
|
||||
self.assertEqual('db', self.secure_boot.identity)
|
||||
self.assertEqual('db - Authorized Signature Database',
|
||||
self.secure_boot.name)
|
||||
|
||||
@mock.patch.object(secure_boot_database.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},
|
||||
self.secure_boot.get_allowed_reset_keys_values())
|
||||
self.assertFalse(mock_log.called)
|
||||
|
||||
@mock.patch.object(secure_boot_database.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},
|
||||
self.secure_boot.get_allowed_reset_keys_values())
|
||||
self.assertTrue(mock_log.called)
|
||||
|
||||
@mock.patch.object(secure_boot_database.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_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/SecureBootDatabases/db'
|
||||
'/Actions/SecureBootDatabase.ResetKeys',
|
||||
data={'ResetKeysType': 'ResetAllKeysToDefault'})
|
||||
|
||||
def test_reset_keys_wrong_value(self):
|
||||
self.assertRaises(exceptions.InvalidParameterValueError,
|
||||
self.secure_boot.reset_keys, 'DeleteEverything')
|
||||
|
||||
|
||||
class SecureBootDatabaseCollectionTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SecureBootDatabaseCollectionTestCase, self).setUp()
|
||||
self.conn = mock.Mock()
|
||||
with open('sushy/tests/unit/json_samples/'
|
||||
'secure_boot_database_collection.json') as f:
|
||||
self.json_doc = json.load(f)
|
||||
|
||||
self.conn.get.return_value.json.return_value = self.json_doc
|
||||
|
||||
self.collection = secure_boot_database.SecureBootDatabaseCollection(
|
||||
self.conn, '/redfish/v1/Systems/437XR1138R2/SecureBootDatabases',
|
||||
redfish_version='1.0.0')
|
||||
|
||||
def test__parse_attributes(self):
|
||||
self.collection._parse_attributes(self.json_doc)
|
||||
self.assertEqual('1.0.0', self.collection.redfish_version)
|
||||
self.assertEqual('UEFI SecureBoot Database Collection',
|
||||
self.collection.name)
|
||||
self.assertEqual(tuple(
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot/SecureBootDatabases/'
|
||||
+ member
|
||||
for member in ('PK', 'KEK', 'db', 'dbx',
|
||||
'PKDefault', 'KEKDefault',
|
||||
'dbDefault', 'dbxDefault')
|
||||
), self.collection.members_identities)
|
||||
|
||||
@mock.patch.object(secure_boot_database, 'SecureBootDatabase',
|
||||
autospec=True)
|
||||
def test_get_member(self, mock_secure_boot_database):
|
||||
self.collection.get_member(
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot'
|
||||
'/SecureBootDatabases/db')
|
||||
mock_secure_boot_database.assert_called_once_with(
|
||||
self.collection._conn,
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot'
|
||||
'/SecureBootDatabases/db',
|
||||
self.collection.redfish_version, None)
|
||||
|
||||
@mock.patch.object(secure_boot_database, 'SecureBootDatabase',
|
||||
autospec=True)
|
||||
def test_get_members(self, mock_secure_boot_database):
|
||||
members = self.collection.get_members()
|
||||
calls = [
|
||||
mock.call(self.collection._conn,
|
||||
'/redfish/v1/Systems/437XR1138R2/SecureBoot'
|
||||
'/SecureBootDatabases/%s' % member,
|
||||
self.collection.redfish_version, None)
|
||||
for member in ('PK', 'KEK', 'db', 'dbx',
|
||||
'PKDefault', 'KEKDefault',
|
||||
'dbDefault', 'dbxDefault')
|
||||
]
|
||||
mock_secure_boot_database.assert_has_calls(calls)
|
||||
self.assertIsInstance(members, list)
|
||||
self.assertEqual(8, len(members))
|
Loading…
Reference in New Issue
Block a user